From 6b6739128b348d4a9b66727e91628c5c55300c04 Mon Sep 17 00:00:00 2001 From: Richard Nemeth Date: Sun, 4 Jan 2026 18:02:27 +0100 Subject: [PATCH] fix: strong and consistent typing across the library BREAKING CHANGE: this change introduces a very strong typing across the library. From now on, we demand that each method returns a single type and converts the results into the specified return type, otherwise it raises a TypeError exception. This should resolve any type checking linter errors within the library as well. --- poetry.lock | 254 +- src/keycloak/authorization/permission.py | 2 +- src/keycloak/authorization/policy.py | 7 +- src/keycloak/authorization/role.py | 2 +- src/keycloak/connection.py | 106 +- src/keycloak/exceptions.py | 16 +- src/keycloak/keycloak_admin.py | 4816 ++++++++++++++++++---- src/keycloak/keycloak_openid.py | 423 +- src/keycloak/keycloak_uma.py | 298 +- src/keycloak/openid_connection.py | 98 +- src/keycloak/uma_permissions.py | 27 +- test_keycloak_init.sh | 16 +- tests/conftest.py | 7 +- tests/test_keycloak_admin.py | 311 +- tests/test_keycloak_openid.py | 84 +- tests/test_keycloak_uma.py | 16 +- tests/test_pkce_flow.py | 4 +- tests/test_uma_permissions.py | 35 +- 18 files changed, 5185 insertions(+), 1337 deletions(-) diff --git a/poetry.lock b/poetry.lock index bbd5a3a..02f4b69 100644 --- a/poetry.lock +++ b/poetry.lock @@ -110,15 +110,15 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [[package]] name = "astroid" -version = "4.0.2" +version = "4.0.3" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.10.0" groups = ["docs"] markers = "python_version >= \"3.12\"" files = [ - {file = "astroid-4.0.2-py3-none-any.whl", hash = "sha256:d7546c00a12efc32650b19a2bb66a153883185d3179ab0d4868086f807338b9b"}, - {file = "astroid-4.0.2.tar.gz", hash = "sha256:ac8fb7ca1c08eb9afec91ccc23edbd8ac73bb22cbdd7da1d488d9fb8d6579070"}, + {file = "astroid-4.0.3-py3-none-any.whl", hash = "sha256:864a0a34af1bd70e1049ba1e61cee843a7252c826d97825fcee9b2fcbd9e1b14"}, + {file = "astroid-4.0.3.tar.gz", hash = "sha256:08d1de40d251cc3dc4a7a12726721d475ac189e4e583d596ece7422bc176bda3"}, ] [[package]] @@ -215,14 +215,14 @@ files = [ [[package]] name = "certifi" -version = "2025.11.12" +version = "2026.1.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev", "docs"] files = [ - {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, - {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, + {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, + {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, ] [[package]] @@ -522,6 +522,7 @@ description = "Python commitizen client tool" optional = false python-versions = "<4.0,>=3.9" groups = ["dev"] +markers = "python_version < \"3.12\"" files = [ {file = "commitizen-4.10.1-py3-none-any.whl", hash = "sha256:ed4a377beed63aa4438f7ad5db791f66e117a5f597677a58b27a1c31e9f64fc4"}, {file = "commitizen-4.10.1.tar.gz", hash = "sha256:14d12252970463db2fa7c7e7e4753321190a093e7d5c99efcd1a63be73e3c1f8"}, @@ -543,6 +544,33 @@ termcolor = ">=1.1.0,<4.0.0" tomlkit = ">=0.8.0,<1.0.0" typing-extensions = {version = ">=4.0.1,<5.0.0", markers = "python_version < \"3.11\""} +[[package]] +name = "commitizen" +version = "4.11.0" +description = "Python commitizen client tool" +optional = false +python-versions = "<4.0,>=3.10" +groups = ["dev"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "commitizen-4.11.0-py3-none-any.whl", hash = "sha256:a30fdf326bb0913b7b25f8b30530c899159b1757efa9969b0aa1808dad102c02"}, + {file = "commitizen-4.11.0.tar.gz", hash = "sha256:d311297a0165ef9f30e0877e04608b786d5fd69760f32245fbf1c21e793e91df"}, +] + +[package.dependencies] +argcomplete = ">=1.12.1,<3.7" +charset-normalizer = ">=2.1.0,<4" +colorama = ">=0.4.1,<1.0" +decli = ">=0.6.0,<1.0" +deprecated = ">=1.2.13,<2" +jinja2 = ">=2.10.3" +packaging = ">=19" +prompt_toolkit = "!=3.0.52" +pyyaml = ">=3.08" +questionary = ">=2.0,<3.0" +termcolor = ">=1.1.0,<4.0.0" +tomlkit = ">=0.8.0,<1.0.0" + [[package]] name = "commonmark" version = "0.9.1" @@ -681,105 +709,105 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "coverage" -version = "7.13.0" +version = "7.13.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_version >= \"3.12\"" files = [ - {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}, - {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}, - {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}, - {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}, - {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}, - {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}, - {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}, - {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}, - {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}, - {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}, - {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}, - {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}, - {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}, - {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}, - {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}, - {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}, - {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}, - {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}, - {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}, - {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}, - {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}, - {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}, - {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}, - {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}, - {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}, - {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}, - {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}, - {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}, - {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}, - {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}, - {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}, - {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}, - {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}, - {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}, - {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}, - {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}, + {file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"}, + {file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"}, + {file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"}, + {file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"}, + {file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"}, + {file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"}, + {file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"}, + {file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"}, + {file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"}, + {file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"}, + {file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"}, + {file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"}, + {file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"}, + {file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"}, + {file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"}, + {file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"}, + {file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"}, + {file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"}, + {file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"}, + {file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"}, + {file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"}, + {file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"}, + {file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"}, + {file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"}, ] [package.extras] @@ -1029,15 +1057,15 @@ files = [ [[package]] name = "filelock" -version = "3.20.1" +version = "3.20.2" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_version >= \"3.12\"" files = [ - {file = "filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a"}, - {file = "filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c"}, + {file = "filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8"}, + {file = "filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64"}, ] [[package]] @@ -2574,15 +2602,15 @@ tests = ["pytest", "pytest-cov"] [[package]] name = "termcolor" -version = "3.2.0" +version = "3.3.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_version >= \"3.12\"" files = [ - {file = "termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6"}, - {file = "termcolor-3.2.0.tar.gz", hash = "sha256:610e6456feec42c4bcd28934a8c87a06c3fa28b01561d46aa09a9881b8622c58"}, + {file = "termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5"}, + {file = "termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5"}, ] [package.extras] @@ -2681,27 +2709,27 @@ virtualenv = ">=20.31.2" [[package]] name = "tox" -version = "4.32.0" +version = "4.33.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_version >= \"3.12\"" files = [ - {file = "tox-4.32.0-py3-none-any.whl", hash = "sha256:451e81dc02ba8d1ed20efd52ee409641ae4b5d5830e008af10fe8823ef1bd551"}, - {file = "tox-4.32.0.tar.gz", hash = "sha256:1ad476b5f4d3679455b89a992849ffc3367560bbc7e9495ee8a3963542e7c8ff"}, + {file = "tox-4.33.0-py3-none-any.whl", hash = "sha256:8582ac5c3ca97095ce88ae6bcd310d22614350ea9751b0e4ad39acad7874e270"}, + {file = "tox-4.33.0.tar.gz", hash = "sha256:a29244bce3f514f94043e173366aa191c8cf0106ec8ddd18ba53f985acd73cc4"}, ] [package.dependencies] -cachetools = ">=6.2" +cachetools = ">=6.2.4" chardet = ">=5.2" colorama = ">=0.4.6" -filelock = ">=3.20" +filelock = ">=3.20.2" packaging = ">=25" -platformdirs = ">=4.5" +platformdirs = ">=4.5.1" pluggy = ">=1.6" -pyproject-api = ">=1.9.1" -virtualenv = ">=20.34" +pyproject-api = ">=1.10" +virtualenv = ">=20.35.4" [[package]] name = "twine" diff --git a/src/keycloak/authorization/permission.py b/src/keycloak/authorization/permission.py index c1e7e3e..dfe0740 100644 --- a/src/keycloak/authorization/permission.py +++ b/src/keycloak/authorization/permission.py @@ -133,7 +133,7 @@ class Permission: return self._logic @logic.setter - def logic(self, value: str) -> str: + def logic(self, value: str) -> None: self._logic = value @property diff --git a/src/keycloak/authorization/policy.py b/src/keycloak/authorization/policy.py index 87cdbbf..90b687c 100644 --- a/src/keycloak/authorization/policy.py +++ b/src/keycloak/authorization/policy.py @@ -24,6 +24,9 @@ from keycloak.exceptions import KeycloakAuthorizationConfigError +from .permission import Permission +from .role import Role + class Policy: """ @@ -172,7 +175,7 @@ class Policy: def permissions(self, value: list) -> None: self._permissions = value - def add_role(self, role: dict) -> None: + def add_role(self, role: str | Role) -> None: """ Add keycloak role in policy. @@ -185,7 +188,7 @@ class Policy: raise KeycloakAuthorizationConfigError(error_msg) self._roles.append(role) - def add_permission(self, permission: dict) -> None: + def add_permission(self, permission: str | Permission) -> None: """ Add keycloak permission in policy. diff --git a/src/keycloak/authorization/role.py b/src/keycloak/authorization/role.py index 7d01f77..141841a 100644 --- a/src/keycloak/authorization/role.py +++ b/src/keycloak/authorization/role.py @@ -61,7 +61,7 @@ class Role: """ return self.name - def __eq__(self, other: str | Role) -> bool: + def __eq__(self, other: object) -> bool: """ Eq method. diff --git a/src/keycloak/connection.py b/src/keycloak/connection.py index 7f736ef..3d1f085 100644 --- a/src/keycloak/connection.py +++ b/src/keycloak/connection.py @@ -27,13 +27,16 @@ from __future__ import annotations try: from urllib.parse import urljoin except ImportError: # pragma: no cover - from urlparse import urljoin + from urlparse import urljoin # pyright: ignore[reportMissingImports] + +from typing import Any import httpx import requests from httpx import Response as AsyncResponse from requests import Response from requests.adapters import HTTPAdapter +from requests_toolbelt import MultipartEncoder from .exceptions import KeycloakConnectionError @@ -67,8 +70,8 @@ class ConnectionManager: self, base_url: str, headers: dict | None = None, - timeout: int = 60, - verify: bool = True, + timeout: int | None = 60, + verify: bool | str = True, proxies: dict | None = None, cert: str | tuple | None = None, max_retries: int = 1, @@ -112,9 +115,13 @@ class ConnectionManager: adapter_kwargs = {"max_retries": max_retries} if pool_maxsize is not None: adapter_kwargs["pool_maxsize"] = pool_maxsize - adapter = HTTPAdapter(**adapter_kwargs) + adapter = HTTPAdapter(**adapter_kwargs) # pyright: ignore[reportArgumentType] # adds POST to retry whitelist - allowed_methods = set(adapter.max_retries.allowed_methods) + allowed_methods = ( + set(adapter.max_retries.allowed_methods) + if adapter.max_retries.allowed_methods + else set() + ) allowed_methods.add("POST") adapter.max_retries.allowed_methods = frozenset(allowed_methods) @@ -132,8 +139,8 @@ class ConnectionManager: max_keepalive_connections=20, ), ) - self.async_s.auth = None # don't let requests add auth headers - self.async_s.transport = httpx.AsyncHTTPTransport(retries=1) + self.async_s.auth = None # pyright: ignore[reportAttributeAccessIssue] + self.async_s.transport = httpx.AsyncHTTPTransport(retries=1) # pyright: ignore[reportAttributeAccessIssue] async def aclose(self) -> None: """Close the async connection on delete.""" @@ -146,7 +153,7 @@ class ConnectionManager: self._s.close() @property - def base_url(self) -> str: + def base_url(self) -> str | None: """ Return base url in use for requests to the server. @@ -156,11 +163,11 @@ class ConnectionManager: return self._base_url @base_url.setter - def base_url(self, value: str) -> None: + def base_url(self, value: str | None) -> None: self._base_url = value @property - def timeout(self) -> int: + def timeout(self) -> int | None: """ Return timeout in use for request to the server. @@ -170,11 +177,11 @@ class ConnectionManager: return self._timeout @timeout.setter - def timeout(self, value: int) -> None: + def timeout(self, value: int | None) -> None: self._timeout = value @property - def verify(self) -> bool: + def verify(self) -> bool | str: """ Return verify in use for request to the server. @@ -184,11 +191,11 @@ class ConnectionManager: return self._verify @verify.setter - def verify(self, value: bool) -> None: + def verify(self, value: bool | str) -> None: self._verify = value @property - def cert(self) -> str | tuple: + def cert(self) -> str | tuple | None: """ Return client certificates in use for request to the server. @@ -198,7 +205,7 @@ class ConnectionManager: return self._cert @cert.setter - def cert(self, value: str | tuple) -> None: + def cert(self, value: str | tuple | None) -> None: self._cert = value @property @@ -216,7 +223,7 @@ class ConnectionManager: self._pool_maxsize = value @property - def headers(self) -> dict: + def headers(self) -> dict | None: """ Return header request to the server. @@ -226,7 +233,7 @@ class ConnectionManager: return self._headers @headers.setter - def headers(self, value: dict) -> None: + def headers(self, value: dict | None) -> None: self._headers = value or {} def param_headers(self, key: str) -> str | None: @@ -238,7 +245,7 @@ class ConnectionManager: :returns: If the header parameters exist, return its value. :rtype: str """ - return self.headers.get(key) + return (self.headers or {}).get(key) def clean_headers(self) -> None: """Clear header parameters.""" @@ -264,6 +271,9 @@ class ConnectionManager: :param value: Value to be added. :type value: str """ + if self.headers is None: + self.headers = {} + self.headers[key] = value def del_param_headers(self, key: str) -> None: @@ -273,9 +283,12 @@ class ConnectionManager: :param key: Key of the header parameters. :type key: str """ + if self.headers is None: + return + self.headers.pop(key, None) - def raw_get(self, path: str, **kwargs: dict) -> Response: + def raw_get(self, path: str, **kwargs: Any) -> Response: # noqa: ANN401 """ Submit get request to the path. @@ -287,6 +300,9 @@ class ConnectionManager: :rtype: Response :raises KeycloakConnectionError: HttpError Can't connect to server. """ + if self.base_url is None: + msg = "Unable to perform GET call with base_url missing." + raise AttributeError(msg) try: return self._s.get( urljoin(self.base_url, path), @@ -300,7 +316,7 @@ class ConnectionManager: msg = "Can't connect to server" raise KeycloakConnectionError(msg) from e - def raw_post(self, path: str, data: dict, **kwargs: dict) -> Response: + def raw_post(self, path: str, data: dict | str | MultipartEncoder, **kwargs: Any) -> Response: # noqa: ANN401 """ Submit post request to the path. @@ -314,6 +330,9 @@ class ConnectionManager: :rtype: Response :raises KeycloakConnectionError: HttpError Can't connect to server. """ + if self.base_url is None: + msg = "Unable to perform POST call with base_url missing." + raise AttributeError(msg) try: return self._s.post( urljoin(self.base_url, path), @@ -328,7 +347,7 @@ class ConnectionManager: msg = "Can't connect to server" raise KeycloakConnectionError(msg) from e - def raw_put(self, path: str, data: dict, **kwargs: dict) -> Response: + def raw_put(self, path: str, data: dict | str, **kwargs: Any) -> Response: # noqa: ANN401 """ Submit put request to the path. @@ -342,6 +361,10 @@ class ConnectionManager: :rtype: Response :raises KeycloakConnectionError: HttpError Can't connect to server. """ + if self.base_url is None: + msg = "Unable to perform PUT call with base_url missing." + raise AttributeError(msg) + try: return self._s.put( urljoin(self.base_url, path), @@ -356,7 +379,7 @@ class ConnectionManager: msg = "Can't connect to server" raise KeycloakConnectionError(msg) from e - def raw_delete(self, path: str, data: dict | None = None, **kwargs: dict) -> Response: + def raw_delete(self, path: str, data: dict | None = None, **kwargs: Any) -> Response: # noqa: ANN401 """ Submit delete request to the path. @@ -370,6 +393,10 @@ class ConnectionManager: :rtype: Response :raises KeycloakConnectionError: HttpError Can't connect to server. """ + if self.base_url is None: + msg = "Unable to perform DELETE call with base_url missing." + raise AttributeError(msg) + try: return self._s.delete( urljoin(self.base_url, path), @@ -384,7 +411,7 @@ class ConnectionManager: msg = "Can't connect to server" raise KeycloakConnectionError(msg) from e - async def a_raw_get(self, path: str, **kwargs: dict) -> AsyncResponse: + async def a_raw_get(self, path: str, **kwargs: Any) -> AsyncResponse: # noqa: ANN401 """ Submit get request to the path. @@ -396,6 +423,10 @@ class ConnectionManager: :rtype: Response :raises KeycloakConnectionError: HttpError Can't connect to server. """ + if self.base_url is None: + msg = "Unable to perform GET call with base_url missing." + raise AttributeError(msg) + try: return await self.async_s.get( urljoin(self.base_url, path), @@ -407,7 +438,12 @@ class ConnectionManager: msg = "Can't connect to server" raise KeycloakConnectionError(msg) from e - async def a_raw_post(self, path: str, data: dict, **kwargs: dict) -> AsyncResponse: + async def a_raw_post( + self, + path: str, + data: dict | str | MultipartEncoder, + **kwargs: Any, # noqa: ANN401 + ) -> AsyncResponse: """ Submit post request to the path. @@ -421,6 +457,10 @@ class ConnectionManager: :rtype: Response :raises KeycloakConnectionError: HttpError Can't connect to server. """ + if self.base_url is None: + msg = "Unable to perform POST call with base_url missing." + raise AttributeError(msg) + try: return await self.async_s.request( method="POST", @@ -434,7 +474,7 @@ class ConnectionManager: msg = "Can't connect to server" raise KeycloakConnectionError(msg) from e - async def a_raw_put(self, path: str, data: dict, **kwargs: dict) -> AsyncResponse: + async def a_raw_put(self, path: str, data: dict | str, **kwargs: Any) -> AsyncResponse: # noqa: ANN401 """ Submit put request to the path. @@ -448,6 +488,10 @@ class ConnectionManager: :rtype: Response :raises KeycloakConnectionError: HttpError Can't connect to server. """ + if self.base_url is None: + msg = "Unable to perform PUT call with base_url missing." + raise AttributeError(msg) + try: return await self.async_s.put( urljoin(self.base_url, path), @@ -464,7 +508,7 @@ class ConnectionManager: self, path: str, data: dict | None = None, - **kwargs: dict, + **kwargs: Any, # noqa: ANN401 ) -> AsyncResponse: """ Submit delete request to the path. @@ -479,6 +523,10 @@ class ConnectionManager: :rtype: Response :raises KeycloakConnectionError: HttpError Can't connect to server. """ + if self.base_url is None: + msg = "Unable to perform DELETE call with base_url missing." + raise AttributeError(msg) + try: return await self.async_s.request( method="DELETE", @@ -493,7 +541,7 @@ class ConnectionManager: raise KeycloakConnectionError(msg) from e @staticmethod - def _prepare_httpx_request_content(data: dict | str | None) -> dict: + def _prepare_httpx_request_content(data: dict | str | None | MultipartEncoder) -> dict: """ Create the correct request content kwarg to `httpx.AsyncClient.request()`. @@ -504,9 +552,13 @@ class ConnectionManager: :returns: A dict mapping the correct kwarg to the request content :rtype: dict """ + if isinstance(data, MultipartEncoder): + return {"content": data.to_string()} + if isinstance(data, str): # Note: this could also accept bytes, Iterable[bytes], or AsyncIterable[bytes] return {"content": data} + return {"data": data} @staticmethod diff --git a/src/keycloak/exceptions.py b/src/keycloak/exceptions.py index 48f36a4..238aafb 100644 --- a/src/keycloak/exceptions.py +++ b/src/keycloak/exceptions.py @@ -57,7 +57,7 @@ class KeycloakError(Exception): def __init__( self, - error_message: str = "", + error_message: str | bytes = "", response_code: int | None = None, response_body: bytes | None = None, ) -> None: @@ -147,7 +147,15 @@ class PermissionDefinitionError(Exception): def raise_error_from_response( response: Response | AsyncResponse, - error: dict | Exception, + error: type[ + KeycloakGetError + | KeycloakPostError + | KeycloakDeprecationError + | KeycloakPutError + | KeycloakDeleteError + ] + | dict + | Exception, expected_codes: list[int] | None = None, skip_exists: bool = False, ) -> bytes | dict | list: @@ -190,9 +198,9 @@ def raise_error_from_response( if isinstance(error, dict): error = error.get(response.status_code, KeycloakOperationError) elif response.status_code == HTTP_UNAUTHORIZED: - error = KeycloakAuthenticationError + error = KeycloakAuthenticationError # pyright: ignore[reportAssignmentType] - raise error( + raise error( # pyright: ignore[reportCallIssue] error_message=message, response_code=response.status_code, response_body=response.content, diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 6ea4bf6..42947aa 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -29,6 +29,7 @@ from __future__ import annotations import copy import json +from typing import Any from requests_toolbelt import MultipartEncoder @@ -100,8 +101,8 @@ class KeycloakAdmin: username: str | None = None, password: str | None = None, token: dict | None = None, - totp: str | None = None, - realm_name: str = "master", + totp: int | None = None, + realm_name: str | None = "master", client_id: str = "admin-cli", verify: bool | str = True, client_secret_key: str | None = None, @@ -226,7 +227,7 @@ class KeycloakAdmin: return results - def __fetch_paginated(self, url: str, query: dict | None = None) -> dict | list: + def __fetch_paginated(self, url: str, query: dict | None = None) -> list: """ Make a specific paginated request. @@ -238,14 +239,21 @@ class KeycloakAdmin: :rtype: dict """ query = query or {} - return raise_error_from_response(self.connection.raw_get(url, **query), KeycloakGetError) + res = raise_error_from_response(self.connection.raw_get(url, **query), KeycloakGetError) + if not isinstance(res, list): + msg = ( + f"Unexpected response type. Expected list, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def get_current_realm(self) -> str: + def get_current_realm(self) -> str | None: """ Return the currently configured realm. :returns: Currently configured realm name - :rtype: str + :rtype: str | None """ return self.connection.realm_name @@ -258,7 +266,7 @@ class KeycloakAdmin: """ self.connection.realm_name = realm_name - def import_realm(self, payload: dict) -> dict | bytes: + def import_realm(self, payload: dict) -> bytes: """ Import a new realm from a RealmRepresentation. @@ -270,19 +278,26 @@ class KeycloakAdmin: :param payload: RealmRepresentation :type payload: dict :return: RealmRepresentation - :rtype: dict + :rtype: bytes """ data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + f"Unexpected response type. Expected bytes, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def partial_import_realm(self, realm_name: str, payload: dict) -> dict | bytes: + def partial_import_realm(self, realm_name: str, payload: dict) -> dict: """ Partial import realm configuration from PartialImportRepresentation. @@ -304,7 +319,14 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_PARTIAL_IMPORT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type. Expected dict, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def export_realm( self, @@ -336,7 +358,14 @@ class KeycloakAdmin: exportClients=export_clients, exportGroupsAndRoles=export_groups_and_role, ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type. Expected dict, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_realms(self) -> list: """ @@ -346,7 +375,14 @@ class KeycloakAdmin: :rtype: list """ data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_REALMS) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + f"Unexpected response type. Expected list, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_realm(self, realm_name: str) -> dict: """ @@ -362,9 +398,16 @@ class KeycloakAdmin: """ params_path = {"realm-name": realm_name} data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_REALM.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type. Expected dict, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def create_realm(self, payload: dict, skip_exists: bool = False) -> dict | bytes: + def create_realm(self, payload: dict, skip_exists: bool = False) -> bytes: """ Create a realm. @@ -382,14 +425,28 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED] + ([HTTP_BAD_REQUEST, HTTP_CONFLICT] if skip_exists else []), ) + if isinstance(res, dict) and res in [ + {"msg": "Already exists"}, + {"errorMessage": "Realm test already exists"}, + {"errorMessage": "Conflict detected. See logs for details"}, + ]: + return json.dumps(res).encode() + + if not isinstance(res, bytes): + msg = ( + f"Unexpected response type. Expected bytes, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_realm(self, realm_name: str, payload: dict) -> dict | bytes: + def update_realm(self, realm_name: str, payload: dict) -> dict: """ Update a realm. @@ -411,13 +468,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type. Expected dict, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_realm(self, realm_name: str) -> dict | bytes: + def delete_realm(self, realm_name: str) -> dict: """ Delete a realm. @@ -428,11 +492,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": realm_name} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_REALM.format(**params_path)) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type. Expected dict, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_organizations(self, query: dict | None = None) -> list: """ @@ -497,7 +568,14 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_ORGANIZATION_BY_ID.format(**params_path) ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type. Expected dict, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_organization(self, organization_id: str) -> dict: """ @@ -520,7 +598,14 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_ORGANIZATION_BY_ID.format(**params_path) ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type. Expected dict, received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_organization(self, payload: dict) -> str | None: """ @@ -576,7 +661,7 @@ class KeycloakAdmin: except KeyError: return None - def update_organization(self, organization_id: str, payload: dict) -> dict | bytes: + def update_organization(self, organization_id: str, payload: dict) -> dict: """ Update an existing organization. @@ -588,7 +673,7 @@ class KeycloakAdmin: :param payload: Dictionary with updated organization details :type payload: dict :return: Response from Keycloak - :rtype: dict | bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -599,11 +684,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_ORGANIZATION_BY_ID.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT] ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_update_organization(self, organization_id: str, payload: dict) -> dict | bytes: + return res + + async def a_update_organization(self, organization_id: str, payload: dict) -> dict: """ Update an existing organization asynchronously. @@ -626,18 +719,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_ORGANIZATION_BY_ID.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT] ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_organization(self, organization_id: str) -> dict | bytes: + def delete_organization(self, organization_id: str) -> dict: """ Delete an organization. :param organization_id: ID of the organization :type organization_id: str :return: Response from Keycloak - :rtype: dict | bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -648,18 +749,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_ORGANIZATION_BY_ID.format(**params_path) ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT] ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_organization(self, organization_id: str) -> dict | bytes: + return res + + async def a_delete_organization(self, organization_id: str) -> dict: """ Delete an organization asynchronously. :param organization_id: ID of the organization :type organization_id: str :return: Response from Keycloak - :rtype: dict | bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -670,9 +779,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_ORGANIZATION_BY_ID.format(**params_path) ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT] ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_organization_idps(self, organization_id: str) -> list: """ @@ -694,7 +811,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_ORGANIZATION_IDPS.format(**params_path) ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_organization_idps(self, organization_id: str) -> list: """ @@ -716,9 +841,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_ORGANIZATION_IDPS.format(**params_path) ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def organization_idp_add(self, organization_id: str, idp_alias: str) -> dict | bytes: + def organization_idp_add(self, organization_id: str, idp_alias: str) -> dict: """ Add an IDP to an organization. @@ -737,11 +870,19 @@ class KeycloakAdmin: data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_ORGANIZATION_IDPS.format(**params_path), data=idp_alias ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT] ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_organization_idp_add(self, organization_id: str, idp_alias: str) -> dict | bytes: + async def a_organization_idp_add(self, organization_id: str, idp_alias: str) -> dict: """ Add an IDP to an organization asynchronously. @@ -750,7 +891,7 @@ class KeycloakAdmin: :param idp_alias: Alias of the IDP :type idp_alias: str :return: Response from Keycloak - :rtype: dict | bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -760,16 +901,25 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_ORGANIZATION_IDPS.format(**params_path), data=idp_alias ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT] ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def organization_idp_remove(self, organization_id: str, idp_alias: str) -> dict | bytes: + def organization_idp_remove(self, organization_id: str, idp_alias: str) -> dict: """ Remove an IDP from an organization. :param organization_id: ID of the organization :type organization_id: str + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -781,20 +931,27 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_ORGANIZATION_IDP_BY_ALIAS.format(**params_path) ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_organization_idp_remove( - self, organization_id: str, idp_alias: str - ) -> dict | bytes: + async def a_organization_idp_remove(self, organization_id: str, idp_alias: str) -> dict: """ Remove an IDP from an organization asynchronously. :param organization_id: ID of the organization :type organization_id: str + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -806,11 +963,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_ORGANIZATION_IDP_BY_ALIAS.format(**params_path) ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_user_organizations(self, user_id: str) -> list: """ @@ -828,7 +993,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_USER_ORGANIZATIONS.format(**params_path) ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_user_organizations(self, user_id: str) -> list: """ @@ -846,7 +1019,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_USER_ORGANIZATIONS.format(**params_path) ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_organization_members(self, organization_id: str, query: dict | None = None) -> list: """ @@ -924,7 +1105,14 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_ORGANIZATION_MEMBERS_COUNT.format(**params_path) ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, int): + msg = ( + f"Unexpected response type. Expected 'int', received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_organization_members_count(self, organization_id: str) -> int: """ @@ -942,9 +1130,16 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_ORGANIZATION_MEMBERS_COUNT.format(**params_path) ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, int): + msg = ( + f"Unexpected response type. Expected 'int', received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def organization_user_add(self, user_id: str, organization_id: str) -> dict | bytes: + def organization_user_add(self, user_id: str, organization_id: str) -> bytes: """ Add a user to an organization. @@ -953,7 +1148,7 @@ class KeycloakAdmin: :param organization_id: ID of the organization :type organization_id: str :return: Response from Keycloak - :rtype: dict | bytes + :rtype: bytes """ params_path = { "realm-name": self.connection.realm_name, @@ -963,11 +1158,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_ORGANIZATION_MEMBERS.format(**params_path), data=user_id ) - return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED] - ) + res = raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED]) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_organization_user_add(self, user_id: str, organization_id: str) -> dict | bytes: + async def a_organization_user_add(self, user_id: str, organization_id: str) -> bytes: """ Add a user to an organization asynchronously. @@ -976,7 +1177,7 @@ class KeycloakAdmin: :param organization_id: ID of the organization :type organization_id: str :return: Response from Keycloak - :rtype: dict | bytes + :rtype: bytes """ params_path = { "realm-name": self.connection.realm_name, @@ -986,11 +1187,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_ORGANIZATION_MEMBERS.format(**params_path), data=user_id ) - return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED] - ) + res = raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED]) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def organization_user_remove(self, user_id: str, organization_id: str) -> dict | bytes: + def organization_user_remove(self, user_id: str, organization_id: str) -> dict: """ Remove a user from an organization. @@ -999,7 +1206,7 @@ class KeycloakAdmin: :param organization_id: ID of the organization :type organization_id: str :return: Response from Keycloak - :rtype: dict | bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -1009,13 +1216,21 @@ class KeycloakAdmin: url = urls_patterns.URL_ADMIN_ORGANIZATION_DEL_MEMBER_BY_ID.format(**params_path) data_raw = self.connection.raw_delete(url) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_organization_user_remove(self, user_id: str, organization_id: str) -> dict | bytes: + return res + + async def a_organization_user_remove(self, user_id: str, organization_id: str) -> dict: """ Remove a user from an organization asynchronously. @@ -1024,7 +1239,7 @@ class KeycloakAdmin: :param organization_id: ID of the organization :type organization_id: str :return: Response from Keycloak - :rtype: dict | bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -1034,11 +1249,19 @@ class KeycloakAdmin: url = urls_patterns.URL_ADMIN_ORGANIZATION_DEL_MEMBER_BY_ID.format(**params_path) data_raw = await self.connection.a_raw_delete(url) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_users(self, query: dict | None = None) -> list: """ @@ -1063,7 +1286,7 @@ class KeycloakAdmin: return self.__fetch_all(url, query) - def create_idp(self, payload: dict) -> dict | bytes: + def create_idp(self, payload: dict) -> bytes: """ Create an ID Provider. @@ -1073,20 +1296,28 @@ class KeycloakAdmin: :param: payload: IdentityProviderRepresentation :type payload: dict :returns: Keycloak server response - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_IDPS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_idp(self, idp_alias: str, payload: dict) -> dict | bytes: + def update_idp(self, idp_alias: str, payload: dict) -> dict: """ Update an ID Provider. @@ -1105,13 +1336,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_IDP.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def add_mapper_to_idp(self, idp_alias: str, payload: dict) -> dict | bytes: + def add_mapper_to_idp(self, idp_alias: str, payload: dict) -> bytes: """ Create an ID Provider. @@ -1123,20 +1362,28 @@ class KeycloakAdmin: :param: payload: IdentityProviderMapperRepresentation :type payload: dict :returns: Keycloak server response - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name, "idp-alias": idp_alias} data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_mapper_in_idp(self, idp_alias: str, mapper_id: str, payload: dict) -> dict | bytes: + def update_mapper_in_idp(self, idp_alias: str, mapper_id: str, payload: dict) -> dict: """ Update an IdP mapper. @@ -1163,11 +1410,19 @@ class KeycloakAdmin: data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_idp_mappers(self, idp_alias: str) -> list: """ @@ -1187,7 +1442,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_idps(self) -> list: """ @@ -1203,7 +1466,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_IDPS.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_idp(self, idp_alias: str) -> dict: """ @@ -1221,9 +1492,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias} data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_IDP.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_idp(self, idp_alias: str) -> dict | bytes: + def delete_idp(self, idp_alias: str) -> dict: """ Delete an ID Provider. @@ -1234,11 +1513,19 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_IDP.format(**params_path)) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_user(self, payload: dict, exist_ok: bool = False) -> str: """ @@ -1292,7 +1579,14 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USERS_COUNT.format(**params_path), **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, int): + msg = ( + f"Unexpected response type. Expected 'int', received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_user_id(self, username: str) -> str | None: """ @@ -1331,7 +1625,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER.format(**params_path), userProfileMetadata=user_profile_metadata, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_user_groups( self, @@ -1368,7 +1670,7 @@ class KeycloakAdmin: return self.__fetch_all(url, query) - def update_user(self, user_id: str, payload: dict) -> dict | bytes: + def update_user(self, user_id: str, payload: dict) -> dict: """ Update the user. @@ -1385,11 +1687,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def disable_user(self, user_id: str) -> dict | bytes: """ @@ -1429,29 +1739,37 @@ class KeycloakAdmin: user_id = user["id"] self.enable_user(user_id=user_id) - def delete_user(self, user_id: str) -> dict | bytes: + def delete_user(self, user_id: str) -> dict: """ Delete the user. :param user_id: User id :type user_id: str :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_USER.format(**params_path)) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def set_user_password( self, user_id: str, password: str, temporary: bool = True, - ) -> dict | bytes: + ) -> dict: """ Set up a password for the user. @@ -1476,11 +1794,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_RESET_PASSWORD.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_credentials(self, user_id: str) -> list: """ @@ -1500,9 +1826,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_USER_CREDENTIALS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_credential(self, user_id: str, credential_id: str) -> dict | bytes: + def delete_credential(self, user_id: str, credential_id: str) -> dict: """ Delete credential of the user. @@ -1514,7 +1848,7 @@ class KeycloakAdmin: :param: credential_id: credential id :type credential_id: str :return: Keycloak server response (ClientRepresentation) - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -1524,9 +1858,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_USER_CREDENTIAL.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def user_logout(self, user_id: str) -> dict | bytes: + def user_logout(self, user_id: str) -> dict: """ Log out the user. @@ -1542,11 +1884,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_LOGOUT.format(**params_path), data="", ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def user_consents(self, user_id: str) -> list: """ @@ -1564,9 +1914,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_USER_CONSENTS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def revoke_consent(self, user_id: str, client_id: str) -> dict | bytes: + def revoke_consent(self, user_id: str, client_id: str) -> dict: """ Revoke consent and offline tokens for particular client from user. @@ -1584,11 +1942,19 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_USER_CONSENT.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_user_social_logins(self, user_id: str) -> list: """ @@ -1605,7 +1971,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def add_user_social_login( self, @@ -1613,7 +1987,7 @@ class KeycloakAdmin: provider_id: str, provider_userid: str, provider_username: str, - ) -> dict | bytes: + ) -> dict: """ Add a federated identity / social login provider to the user. @@ -1642,13 +2016,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED, HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_user_social_login(self, user_id: str, provider_id: str) -> dict | bytes: + def delete_user_social_login(self, user_id: str, provider_id: str) -> dict: """ Delete a federated identity / social login provider from the user. @@ -1657,7 +2039,7 @@ class KeycloakAdmin: :param provider_id: Social login provider id :type provider_id: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -1667,11 +2049,19 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def send_update_account( self, @@ -1680,7 +2070,7 @@ class KeycloakAdmin: client_id: str | None = None, lifespan: int | None = None, redirect_uri: str | None = None, - ) -> dict | bytes: + ) -> dict: """ Send an update account email to the user. @@ -1698,7 +2088,7 @@ class KeycloakAdmin: :type redirect_uri: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} params_query = {"client_id": client_id, "lifespan": lifespan, "redirect_uri": redirect_uri} @@ -1707,14 +2097,22 @@ class KeycloakAdmin: data=json.dumps(payload), **params_query, ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def send_verify_email( self, user_id: str, client_id: str | None = None, redirect_uri: str | None = None, - ) -> dict | bytes: + ) -> dict: """ Send a update account email to the user. @@ -1728,7 +2126,7 @@ class KeycloakAdmin: :type redirect_uri: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} params_query = {"client_id": client_id, "redirect_uri": redirect_uri} @@ -1737,7 +2135,15 @@ class KeycloakAdmin: data={}, **params_query, ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_sessions(self, user_id: str) -> list: """ @@ -1755,7 +2161,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_GET_SESSIONS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_server_info(self) -> dict: """ @@ -1768,7 +2182,15 @@ class KeycloakAdmin: :rtype: dict """ data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_SERVER_INFO) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_groups(self, query: dict | None = None, full_hierarchy: bool = False) -> list: """ @@ -1831,7 +2253,7 @@ class KeycloakAdmin: response = self.connection.raw_get(urls_patterns.URL_ADMIN_GROUP.format(**params_path)) if response.status_code >= HTTP_BAD_REQUEST: - return raise_error_from_response(response, KeycloakGetError) + raise_error_from_response(response, KeycloakGetError) # For version +23.0.0 group = response.json() @@ -1843,7 +2265,7 @@ class KeycloakAdmin: return group - def get_subgroups(self, group: str, path: str) -> dict | None: + def get_subgroups(self, group: dict, path: str) -> dict | None: """ Get subgroups. @@ -1876,7 +2298,7 @@ class KeycloakAdmin: group_id: str, query: dict | None = None, full_hierarchy: bool = False, - ) -> dict: + ) -> list: """ Get group children by parent id. @@ -1889,7 +2311,7 @@ class KeycloakAdmin: :param full_hierarchy: If True, return all of the nested children groups :type full_hierarchy: bool :return: Keycloak server response (GroupRepresentation) - :rtype: dict + :rtype: list :raises ValueError: If both query and full_hierarchy parameters are used """ query = query or {} @@ -1901,6 +2323,7 @@ class KeycloakAdmin: url = urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path) if "first" in query or "max" in query: return self.__fetch_paginated(url, query) + res = self.__fetch_all(url, query) if not full_hierarchy: @@ -1965,7 +2388,15 @@ class KeycloakAdmin: # added `HTTP_NOT_FOUND` to the `expected_codes` argument. # This change has since been reverted, see: # https://github.com/marcospereirampj/python-keycloak/issues/675 - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_group( self, @@ -2014,7 +2445,7 @@ class KeycloakAdmin: except KeyError: return None - def update_group(self, group_id: str, payload: dict) -> dict | bytes: + def update_group(self, group_id: str, payload: dict) -> dict: """ Update group, ignores subgroups. @@ -2027,18 +2458,26 @@ class KeycloakAdmin: :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_GROUP.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def groups_count(self, query: dict | None = None) -> dict: """ @@ -2058,9 +2497,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_COUNT.format(**params_path), **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def group_set_permissions(self, group_id: str, enabled: bool = True) -> bytes: + def group_set_permissions(self, group_id: str, enabled: bool = True) -> dict: """ Enable/Disable permissions for a group. @@ -2071,16 +2518,24 @@ class KeycloakAdmin: :param enabled: Enabled flag :type enabled: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_GROUP_PERMISSIONS.format(**params_path), data=json.dumps({"enabled": enabled}), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def group_user_add(self, user_id: str, group_id: str) -> bytes: + def group_user_add(self, user_id: str, group_id: str) -> dict: """ Add user to group (user_id and group_id). @@ -2089,7 +2544,7 @@ class KeycloakAdmin: :param group_id: id of group to add to :type group_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -2100,13 +2555,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), data=None, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def group_user_remove(self, user_id: str, group_id: str) -> bytes: + return res + + def group_user_remove(self, user_id: str, group_id: str) -> dict: """ Remove user from group (user_id and group_id). @@ -2115,7 +2578,7 @@ class KeycloakAdmin: :param group_id: id of group to remove from :type group_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -2125,28 +2588,44 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_group(self, group_id: str) -> dict | bytes: + def delete_group(self, group_id: str) -> dict: """ Delete a group in the Realm. :param group_id: id of group to delete :type group_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_GROUP.format(**params_path)) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_clients(self) -> list: """ @@ -2162,7 +2641,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_CLIENTS.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client(self, client_id: str) -> dict: """ @@ -2178,7 +2665,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_CLIENT.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_id(self, client_id: str) -> str | None: """ @@ -2198,6 +2693,12 @@ class KeycloakAdmin: clientId=client_id, ) data_response = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(data_response, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(data_response)}', value '{data_response}'." + ) + raise TypeError(msg) for client in data_response: if client_id == client.get("clientId"): @@ -2219,7 +2720,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def import_client_authz_config(self, client_id: str, payload: dict) -> dict: """ @@ -2242,18 +2751,26 @@ class KeycloakAdmin: data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_client_authz_resource( self, client_id: str, payload: dict, skip_exists: bool = False, - ) -> dict | bytes: + ) -> dict: """ Create resources of client. @@ -2267,7 +2784,7 @@ class KeycloakAdmin: :type skip_exists: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} @@ -2276,19 +2793,27 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def update_client_authz_resource( self, client_id: str, resource_id: str, payload: dict, - ) -> dict | bytes: + ) -> dict: """ Update resource of client. @@ -2320,13 +2845,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_client_authz_resource(self, client_id: str, resource_id: str) -> bytes: + def delete_client_authz_resource(self, client_id: str, resource_id: str) -> dict: """ Delete a client resource. @@ -2338,7 +2871,7 @@ class KeycloakAdmin: :type resource_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -2348,11 +2881,19 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_resources(self, client_id: str) -> list: """ @@ -2369,9 +2910,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path), max=-1, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def get_client_authz_resource(self, client_id: str, resource_id: str) -> dict | bytes: + def get_client_authz_resource(self, client_id: str, resource_id: str) -> dict: """ Get a client resource. @@ -2393,14 +2942,22 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_client_authz_role_based_policy( self, client_id: str, payload: dict, skip_exists: bool = False, - ) -> dict | bytes: + ) -> dict: """ Create role-based policy of client. @@ -2436,19 +2993,27 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_client_authz_policy( self, client_id: str, payload: dict, skip_exists: bool = False, - ) -> dict | bytes: + ) -> dict: """ Create an authz policy of client. @@ -2473,7 +3038,7 @@ class KeycloakAdmin: :param skip_exists: Skip creation in case the object exists :type skip_exists: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} @@ -2484,19 +3049,27 @@ class KeycloakAdmin: max=-1, permission=False, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_client_authz_resource_based_permission( self, client_id: str, payload: dict, skip_exists: bool = False, - ) -> bytes: + ) -> dict: """ Create resource-based permission of client. @@ -2523,7 +3096,7 @@ class KeycloakAdmin: :param skip_exists: Skip creation in case the object already exists :type skip_exists: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} @@ -2533,12 +3106,20 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_scopes(self, client_id: str) -> list: """ @@ -2555,9 +3136,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path), max=-1, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def create_client_authz_scopes(self, client_id: str, payload: dict) -> bytes: + return res + + def create_client_authz_scopes(self, client_id: str, payload: dict) -> dict: """ Create scopes for client. @@ -2568,7 +3157,7 @@ class KeycloakAdmin: :type payload: dict :type client_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_post( @@ -2576,11 +3165,19 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_permissions(self, client_id: str) -> list: """ @@ -2597,7 +3194,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_PERMISSIONS.format(**params_path), max=-1, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_policies(self, client_id: str) -> list: """ @@ -2615,9 +3220,17 @@ class KeycloakAdmin: max=-1, permission=False, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_client_authz_policy(self, client_id: str, policy_id: str) -> dict | bytes: + def delete_client_authz_policy(self, client_id: str, policy_id: str) -> dict: """ Delete a policy from client. @@ -2638,11 +3251,19 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_policy(self, client_id: str, policy_id: str) -> dict: """ @@ -2665,7 +3286,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_service_account_user(self, client_id: str) -> dict: """ @@ -2681,7 +3310,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_default_client_scopes(self, client_id: str) -> list: """ @@ -2697,14 +3334,22 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def add_client_default_client_scope( self, client_id: str, client_scope_id: str, payload: dict, - ) -> bytes: + ) -> dict: """ Add a client scope to the default client scopes from client. @@ -2724,7 +3369,7 @@ class KeycloakAdmin: :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -2735,13 +3380,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def delete_client_default_client_scope( self, client_id: str, client_scope_id: str, - ) -> dict | bytes: + ) -> dict: """ Delete a client scope from the default client scopes of the client. @@ -2751,7 +3404,7 @@ class KeycloakAdmin: :type client_scope_id: str :return: list of client scopes with id and name - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -2761,7 +3414,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_optional_client_scopes(self, client_id: str) -> list: """ @@ -2777,14 +3438,22 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def add_client_optional_client_scope( self, client_id: str, client_scope_id: str, payload: dict, - ) -> bytes: + ) -> dict: """ Add a client scope to the optional client scopes from client. @@ -2804,7 +3473,7 @@ class KeycloakAdmin: :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -2815,13 +3484,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def delete_client_optional_client_scope( self, client_id: str, client_scope_id: str, - ) -> dict | bytes: + ) -> dict: """ Delete a client scope from the optional client scopes of the client. @@ -2831,7 +3508,7 @@ class KeycloakAdmin: :type client_scope_id: str :return: list of client scopes with id and name - :rtype: list + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -2841,9 +3518,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def create_initial_access_token(self, count: int = 1, expiration: int = 1) -> dict | bytes: + return res + + def create_initial_access_token(self, count: int = 1, expiration: int = 1) -> dict: """ Create an initial access token. @@ -2860,7 +3545,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_INITIAL_ACCESS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_client(self, payload: dict, skip_exists: bool = False) -> str: """ @@ -2896,7 +3589,7 @@ class KeycloakAdmin: _last_slash_idx = data_raw.headers["Location"].rindex("/") return data_raw.headers["Location"][_last_slash_idx + 1 :] - def update_client(self, client_id: str, payload: dict) -> bytes: + def update_client(self, client_id: str, payload: dict) -> dict: """ Update a client. @@ -2906,20 +3599,28 @@ class KeycloakAdmin: :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def delete_client(self, client_id: str) -> bytes: + return res + + def delete_client(self, client_id: str) -> dict: """ Get representation of the client. @@ -2929,17 +3630,25 @@ class KeycloakAdmin: :param client_id: keycloak client id (not oauth client-id) :type client_id: str :return: Keycloak server response (ClientRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_CLIENT.format(**params_path)) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def get_client_installation_provider(self, client_id: str, provider_id: str) -> list: + def get_client_installation_provider(self, client_id: str, provider_id: str) -> dict: """ Get content for given installation provider. @@ -2954,7 +3663,7 @@ class KeycloakAdmin: :param provider_id: provider id to specify response format :type provider_id: str :returns: Installation providers - :rtype: list + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -2964,7 +3673,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_INSTALLATION_PROVIDER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_realm_users_profile(self) -> dict: """ @@ -2982,7 +3699,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_REALM_USER_PROFILE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_realm_roles( self, brief_representation: bool = True, search_text: str = "", query: dict | None = None @@ -3004,7 +3729,7 @@ class KeycloakAdmin: """ query = query or {} params_path = {"realm-name": self.connection.realm_name} - params = {"briefRepresentation": brief_representation} + params: dict[str, str | bool] = {"briefRepresentation": brief_representation} url = urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path) if search_text is not None and search_text.strip() != "": @@ -3097,9 +3822,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES_REALM.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def remove_realm_default_roles(self, payload: dict) -> dict | bytes: + def remove_realm_default_roles(self, payload: list) -> dict: """ Remove a set of default realm roles. @@ -3116,9 +3849,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def add_realm_default_roles(self, payload: dict) -> dict | bytes: + return res + + def add_realm_default_roles(self, payload: list) -> dict: """ Add a set of default realm roles. @@ -3135,7 +3876,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_roles(self, client_id: str, brief_representation: bool = True) -> list: """ @@ -3157,7 +3906,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_role(self, client_id: str, role_name: str) -> dict: """ @@ -3181,7 +3938,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_role_id(self, client_id: str, role_name: str) -> str | None: """ @@ -3249,7 +4014,7 @@ class KeycloakAdmin: client_role_id: str, role_name: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Add composite roles to client role. @@ -3272,18 +4037,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def remove_composite_client_roles_from_role( self, client_role_id: str, role_name: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Remove composite roles from a client role. @@ -3294,7 +4067,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) to be removed :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -3306,13 +4079,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_client_role(self, client_id: str, role_name: str, payload: dict) -> bytes: + def update_client_role(self, client_id: str, role_name: str, payload: dict) -> dict: """ Update a client role. @@ -3326,7 +4107,7 @@ class KeycloakAdmin: :param payload: RoleRepresentation :type payload: dict :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -3337,13 +4118,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_client_role(self, client_role_id: str, role_name: str) -> bytes: + def delete_client_role(self, client_role_id: str, role_name: str) -> dict: """ Delete a client role. @@ -3355,7 +4144,7 @@ class KeycloakAdmin: :param role_name: role's name (not id!) :type role_name: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -3365,13 +4154,21 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def assign_client_role(self, user_id: str, client_id: str, roles: str | list) -> bytes: + def assign_client_role(self, user_id: str, client_id: str, roles: str | list) -> dict: """ Assign a client role to a user. @@ -3382,7 +4179,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -3394,13 +4191,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def get_client_role_members(self, client_id: str, role_name: str, **query: dict) -> list: + return res + + def get_client_role_members(self, client_id: str, role_name: str, **query: Any) -> list: # noqa: ANN401 """ Get members by client role. @@ -3424,7 +4229,7 @@ class KeycloakAdmin: query, ) - def get_client_role_groups(self, client_id: str, role_name: str, **query: dict) -> list: + def get_client_role_groups(self, client_id: str, role_name: str, **query: Any) -> list: # noqa: ANN401 """ Get group members by client role. @@ -3464,9 +4269,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_role_by_id(self, role_id: str, payload: dict) -> bytes: + def update_role_by_id(self, role_id: str, payload: dict) -> dict: """ Update the role. @@ -3478,20 +4291,28 @@ class KeycloakAdmin: :param role_id: id of role :type role_id: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def delete_role_by_id(self, role_id: str) -> bytes: + return res + + def delete_role_by_id(self, role_id: str) -> dict: """ Delete a role by its id. @@ -3501,17 +4322,25 @@ class KeycloakAdmin: :param role_id: id of role :type role_id: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_role_composites_by_id(self, role_id: str, query: dict | None = None) -> list: """ @@ -3581,7 +4410,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_realm_role_by_id(self, role_id: str) -> dict: """ @@ -3599,9 +4436,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_realm_role(self, role_name: str, payload: dict) -> bytes: + def update_realm_role(self, role_name: str, payload: dict) -> dict: """ Update a role for the realm by name. @@ -3617,11 +4462,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def update_realm_users_profile(self, payload: dict) -> dict: """ @@ -3637,32 +4490,48 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_USER_PROFILE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_OK], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_realm_role(self, role_name: str) -> bytes: + def delete_realm_role(self, role_name: str) -> dict: """ Delete a role for the realm by name. :param role_name: The role name :type role_name: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def add_composite_realm_roles_to_role(self, role_name: str, roles: str | list) -> bytes: + def add_composite_realm_roles_to_role(self, role_name: str, roles: str | list) -> dict: """ Add composite roles to the role. @@ -3671,7 +4540,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) to be updated :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} @@ -3679,13 +4548,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def remove_composite_realm_roles_to_role(self, role_name: str, roles: str | list) -> bytes: + def remove_composite_realm_roles_to_role(self, role_name: str, roles: str | list) -> dict: """ Remove composite roles from the role. @@ -3694,7 +4571,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) to be removed :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} @@ -3702,11 +4579,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_composite_realm_roles_of_role(self, role_name: str) -> list: """ @@ -3721,7 +4606,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_composite_client_roles_of_role(self, client_id: str, role_name: str) -> list: """ @@ -3742,9 +4635,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def assign_realm_roles_to_client_scope(self, client_id: str, roles: str | list) -> bytes: + def assign_realm_roles_to_client_scope(self, client_id: str, roles: str | list) -> dict: """ Assign realm roles to a client's scope. @@ -3753,7 +4654,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": client_id} @@ -3761,13 +4662,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_realm_roles_of_client_scope(self, client_id: str, roles: str | list) -> bytes: + def delete_realm_roles_of_client_scope(self, client_id: str, roles: str | list) -> dict: """ Delete realm roles of a client's scope. @@ -3784,11 +4693,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_realm_roles_of_client_scope(self, client_id: str) -> list: """ @@ -3803,14 +4720,22 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def assign_client_roles_to_client_scope( self, client_id: str, client_roles_owner_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Assign client roles to a client's dedicated scope. @@ -3823,7 +4748,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -3835,18 +4760,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def delete_client_roles_of_client_scope( self, client_id: str, client_roles_owner_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Delete client roles of a client's dedicated scope. @@ -3859,7 +4792,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -3871,11 +4804,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_roles_of_client_scope(self, client_id: str, client_roles_owner_id: str) -> list: """ @@ -3898,9 +4839,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def assign_realm_roles(self, user_id: str, roles: str | list) -> bytes: + def assign_realm_roles(self, user_id: str, roles: str | list) -> dict: """ Assign realm roles to a user. @@ -3909,7 +4858,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": user_id} @@ -3917,13 +4866,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_realm_roles_of_user(self, user_id: str, roles: str | list) -> bytes: + def delete_realm_roles_of_user(self, user_id: str, roles: str | list) -> dict: """ Delete realm roles of a user. @@ -3932,7 +4889,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": user_id} @@ -3940,11 +4897,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_realm_roles_of_user(self, user_id: str) -> list: """ @@ -3959,7 +4924,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_available_realm_roles_of_user(self, user_id: str) -> list: """ @@ -3974,7 +4947,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_USER_REALM_ROLES_AVAILABLE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_composite_realm_roles_of_user( self, @@ -3997,9 +4978,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def assign_group_realm_roles(self, group_id: str, roles: str | list) -> bytes: + def assign_group_realm_roles(self, group_id: str, roles: str | list) -> dict: """ Assign realm roles to a group. @@ -4008,7 +4997,7 @@ class KeycloakAdmin: :param roles: roles list or role (use GroupRoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": group_id} @@ -4016,13 +5005,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_group_realm_roles(self, group_id: str, roles: str | list) -> bytes: + def delete_group_realm_roles(self, group_id: str, roles: str | list) -> dict: """ Delete realm roles of a group. @@ -4031,7 +5028,7 @@ class KeycloakAdmin: :param roles: roles list or role (use GroupRoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": group_id} @@ -4039,13 +5036,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def get_group_realm_roles(self, group_id: str, brief_representation: bool = True) -> list: + return res + + def get_group_realm_roles(self, group_id: str, brief_representation: bool = True) -> list: """ Get all realm roles for a group. @@ -4062,9 +5067,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def assign_group_client_roles(self, group_id: str, client_id: str, roles: str | list) -> bytes: + return res + + def assign_group_client_roles(self, group_id: str, client_id: str, roles: str | list) -> dict: """ Assign client roles to a group. @@ -4075,7 +5088,7 @@ class KeycloakAdmin: :param roles: roles list or role (use GroupRoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -4087,11 +5100,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_group_client_roles(self, group_id: str, client_id: str) -> list: """ @@ -4112,9 +5133,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def delete_group_client_roles(self, group_id: str, client_id: str, roles: str | list) -> bytes: + return res + + def delete_group_client_roles(self, group_id: str, client_id: str, roles: str | list) -> dict: """ Delete client roles of a group. @@ -4125,7 +5154,7 @@ class KeycloakAdmin: :param roles: roles list or role (use GroupRoleRepresentation) :type roles: list :return: Keycloak server response (array RoleRepresentation) - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -4137,11 +5166,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_all_roles_of_user(self, user_id: str) -> dict: """ @@ -4156,7 +5193,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_USER_ALL_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_roles_of_user(self, user_id: str, client_id: str) -> list: """ @@ -4223,7 +5268,7 @@ class KeycloakAdmin: client_level_role_mapping_url: str, user_id: str, client_id: str, - **params: dict, + **params: Any, # noqa: ANN401 ) -> list: """ Get client roles of a single user helper. @@ -4248,14 +5293,22 @@ class KeycloakAdmin: client_level_role_mapping_url.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def delete_client_roles_of_user( self, user_id: str, client_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Delete client roles from a user. @@ -4266,7 +5319,7 @@ class KeycloakAdmin: :param roles: roles list or role to delete (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -4278,11 +5331,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_authentication_flows(self) -> list: """ @@ -4298,7 +5359,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_FLOWS.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_authentication_flow_for_id(self, flow_id: str) -> dict: """ @@ -4318,9 +5387,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_FLOWS_ALIAS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def create_authentication_flow(self, payload: dict, skip_exists: bool = False) -> bytes: + return res + + def create_authentication_flow(self, payload: dict, skip_exists: bool = False) -> bytes | dict: """ Create a new authentication flow. @@ -4332,21 +5409,32 @@ class KeycloakAdmin: :param skip_exists: Do not raise an error if authentication flow already exists :type skip_exists: bool :return: Keycloak server response (RoleRepresentation) - :rtype: bytes + :rtype: bytes | dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_FLOWS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if isinstance(res, dict) and res == {"msg": "Already exists"}: + return res - def update_authentication_flow(self, flow_id: str, payload: dict) -> bytes: + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res + + def update_authentication_flow(self, flow_id: str, payload: dict) -> dict: """ Update an authentication flow. @@ -4358,18 +5446,26 @@ class KeycloakAdmin: :param payload: AuthenticationFlowRepresentation :type payload: dict :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"id": flow_id, "realm-name": self.connection.realm_name} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_FLOW.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_ACCEPTED, HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def copy_authentication_flow(self, payload: dict, flow_alias: str) -> bytes: """ @@ -4389,13 +5485,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_COPY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_authentication_flow(self, flow_id: str) -> bytes: + def delete_authentication_flow(self, flow_id: str) -> dict: """ Delete authentication flow. @@ -4405,15 +5509,23 @@ class KeycloakAdmin: :param flow_id: authentication flow id :type flow_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": flow_id} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_FLOW.format(**params_path)) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_authentication_flow_executions(self, flow_alias: str) -> list: """ @@ -4430,9 +5542,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_authentication_flow_executions(self, payload: dict, flow_alias: str) -> bytes: + def update_authentication_flow_executions(self, payload: dict, flow_alias: str) -> dict: """ Update an authentication flow execution. @@ -4444,20 +5564,28 @@ class KeycloakAdmin: :param flow_alias: The flow alias :type flow_alias: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_ACCEPTED, HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def get_authentication_flow_execution(self, execution_id: str) -> list: + def get_authentication_flow_execution(self, execution_id: str) -> dict: """ Get authentication flow execution. @@ -4473,7 +5601,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_authentication_flow_execution(self, payload: dict, flow_alias: str) -> bytes: """ @@ -4494,13 +5630,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_EXECUTION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_authentication_flow_execution(self, execution_id: str) -> bytes: + def delete_authentication_flow_execution(self, execution_id: str) -> dict: """ Delete authentication flow execution. @@ -4510,17 +5654,25 @@ class KeycloakAdmin: :param execution_id: keycloak client id (not oauth client-id) :type execution_id: str :return: Keycloak server response (json) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": execution_id} data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def change_execution_priority(self, execution_id: str, diff: int) -> None: """ @@ -4587,12 +5739,23 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_FLOW.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if isinstance(res, dict) and res == {"msg": "Already exists"}: + return json.dumps(res).encode() + + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_authenticator_providers(self) -> list: """ @@ -4605,7 +5768,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_AUTHENTICATOR_PROVIDERS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_authenticator_provider_config_description(self, provider_id: str) -> dict: """ @@ -4623,7 +5794,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG_DESCRIPTION.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_authenticator_config(self, config_id: str) -> dict: """ @@ -4640,7 +5819,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_execution_config(self, execution_id: str, payload: dict) -> bytes: """ @@ -4654,20 +5841,28 @@ class KeycloakAdmin: :param payload: Configuration to add to the execution :type payload: dir :return: Response(json) - :rtype: dict + :rtype: bytes """ params_path = {"id": execution_id, "realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_FLOWS_EXECUTION_CONFIG.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def update_authenticator_config(self, payload: dict, config_id: str) -> bytes: + return res + + def update_authenticator_config(self, payload: dict, config_id: str) -> dict: """ Update an authenticator configuration. @@ -4679,20 +5874,28 @@ class KeycloakAdmin: :param config_id: Authenticator config id :type config_id: str :return: Response(json) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": config_id} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_authenticator_config(self, config_id: str) -> bytes: + def delete_authenticator_config(self, config_id: str) -> dict: """ Delete a authenticator configuration. @@ -4701,19 +5904,27 @@ class KeycloakAdmin: :param config_id: Authenticator config id :type config_id: str :return: Keycloak server Response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": config_id} data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def sync_users(self, storage_id: str, action: str) -> bytes: + def sync_users(self, storage_id: str, action: str) -> dict: """ Trigger user sync from provider. @@ -4722,7 +5933,7 @@ class KeycloakAdmin: :param action: Action can be "triggerFullSync" or "triggerChangedUsersSync" :type action: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ data = {"action": action} params_query = {"action": action} @@ -4733,7 +5944,15 @@ class KeycloakAdmin: data=json.dumps(data), **params_query, ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_scopes(self) -> list: """ @@ -4749,7 +5968,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_scope(self, client_scope_id: str) -> dict: """ @@ -4767,9 +5994,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def get_client_scope_by_name(self, client_scope_name: str) -> dict: + def get_client_scope_by_name(self, client_scope_name: str) -> dict | None: """ Get client scope by name. @@ -4822,7 +6057,7 @@ class KeycloakAdmin: _last_slash_idx = data_raw.headers["Location"].rindex("/") return data_raw.headers["Location"][_last_slash_idx + 1 :] - def update_client_scope(self, client_scope_id: str, payload: dict) -> bytes: + def update_client_scope(self, client_scope_id: str, payload: dict) -> dict: """ Update a client scope. @@ -4834,20 +6069,28 @@ class KeycloakAdmin: :param payload: ClientScopeRepresentation :type payload: dict :return: Keycloak server response (ClientScopeRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_client_scope(self, client_scope_id: str) -> bytes: + def delete_client_scope(self, client_scope_id: str) -> dict: """ Delete existing client scope. @@ -4857,17 +6100,25 @@ class KeycloakAdmin: :param client_scope_id: The id of the client scope :type client_scope_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_mappers_from_client_scope(self, client_scope_id: str) -> list: """ @@ -4883,7 +6134,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def add_mapper_to_client_scope(self, client_scope_id: str, payload: dict) -> bytes: """ @@ -4903,17 +6162,25 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def delete_mapper_from_client_scope( self, client_scope_id: str, protocol_mapper_id: str, - ) -> bytes: + ) -> dict: """ Delete a mapper from a client scope. @@ -4924,7 +6191,7 @@ class KeycloakAdmin: :param protocol_mapper_id: Protocol mapper id :type protocol_mapper_id: str :return: Keycloak server Response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -4934,18 +6201,26 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def update_mapper_in_client_scope( self, client_scope_id: str, protocol_mapper_id: str, payload: dict, - ) -> bytes: + ) -> dict: """ Update an existing protocol mapper in a client scope. @@ -4959,7 +6234,7 @@ class KeycloakAdmin: :param payload: ProtocolMapperRepresentation :type payload: dict :return: Keycloak server Response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -4970,11 +6245,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_default_default_client_scopes(self) -> list: """ @@ -4989,35 +6272,51 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_default_default_client_scope(self, scope_id: str) -> bytes: + def delete_default_default_client_scope(self, scope_id: str) -> dict: """ Delete default default client scope. :param scope_id: default default client scope id :type scope_id: str :return: Keycloak server response - :rtype: list + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def add_default_default_client_scope(self, scope_id: str) -> bytes: + def add_default_default_client_scope(self, scope_id: str) -> dict: """ Add default default client scope. :param scope_id: default default client scope id :type scope_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} payload = {"realm": self.connection.realm_name, "clientScopeId": scope_id} @@ -5025,11 +6324,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_default_optional_client_scopes(self) -> list: """ @@ -5044,35 +6351,51 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_default_optional_client_scope(self, scope_id: str) -> bytes: + def delete_default_optional_client_scope(self, scope_id: str) -> dict: """ Delete default optional client scope. :param scope_id: default optional client scope id :type scope_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def add_default_optional_client_scope(self, scope_id: str) -> bytes: + return res + + def add_default_optional_client_scope(self, scope_id: str) -> dict: """ Add default optional client scope. :param scope_id: default optional client scope id :type scope_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} payload = {"realm": self.connection.realm_name, "clientScopeId": scope_id} @@ -5080,18 +6403,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def add_client_specific_roles_to_client_scope( self, client_scope_id: str, client_roles_owner_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Assign client roles to a client scope. @@ -5104,7 +6435,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation, must include id and name) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -5116,18 +6447,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def remove_client_specific_roles_of_client_scope( self, client_scope_id: str, client_roles_owner_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Delete client roles of a client scope. @@ -5140,7 +6479,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation, must include id and name) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -5152,11 +6491,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_specific_roles_of_client_scope( self, @@ -5183,9 +6530,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def get_all_roles_of_client_scope(self, client_scope_id: str) -> list: + def get_all_roles_of_client_scope(self, client_scope_id: str) -> dict: """ Get all client roles for a client scope. @@ -5201,7 +6556,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_mappers_from_client(self, client_id: str) -> list: """ @@ -5218,7 +6581,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def add_mapper_to_client(self, client_id: str, payload: dict) -> bytes: """ @@ -5238,13 +6609,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_client_mapper(self, client_id: str, mapper_id: str, payload: dict) -> bytes: + def update_client_mapper(self, client_id: str, mapper_id: str, payload: dict) -> dict: """ Update client mapper. @@ -5255,7 +6634,7 @@ class KeycloakAdmin: :param payload: ProtocolMapperRepresentation :type payload: dict :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -5266,13 +6645,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def remove_client_mapper(self, client_id: str, client_mapper_id: str) -> bytes: + return res + + def remove_client_mapper(self, client_id: str, client_mapper_id: str) -> dict: """ Remove a mapper from the client. @@ -5293,13 +6680,21 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def generate_client_secrets(self, client_id: str) -> bytes: + def generate_client_secrets(self, client_id: str) -> dict: """ Generate a new secret for the client. @@ -5308,14 +6703,22 @@ class KeycloakAdmin: :param client_id: id of client (not client-id) :type client_id: str :return: Keycloak server response (ClientRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), data=None, ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_secrets(self, client_id: str) -> dict: """ @@ -5332,7 +6735,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_components(self, query: dict | None = None) -> list: """ @@ -5355,7 +6766,15 @@ class KeycloakAdmin: data=None, **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def create_component(self, payload: dict) -> str: """ @@ -5394,9 +6813,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_COMPONENT.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_component(self, component_id: str, payload: dict) -> bytes: + def update_component(self, component_id: str, payload: dict) -> dict: """ Update the component. @@ -5406,39 +6833,55 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_componentrepresentation :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def delete_component(self, component_id: str) -> bytes: + def delete_component(self, component_id: str) -> dict: """ Delete the component. :param component_id: Component id :type component_id: str :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def get_keys(self) -> list: + def get_keys(self) -> dict: """ Get keys. @@ -5448,14 +6891,22 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_key_resource :return: keys list - :rtype: list + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_KEYS.format(**params_path), data=None, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_admin_events(self, query: dict | None = None) -> list: """ @@ -5479,7 +6930,15 @@ class KeycloakAdmin: data=None, **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_events(self, query: dict | None = None) -> list: """ @@ -5502,9 +6961,17 @@ class KeycloakAdmin: data=None, **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def set_events(self, payload: dict) -> bytes: + def set_events(self, payload: dict) -> dict: """ Set realm events configuration. @@ -5514,18 +6981,26 @@ class KeycloakAdmin: :param payload: Payload object for the events configuration :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_EVENTS_CONFIG.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_all_sessions(self, client_id: str, query: dict | None = None) -> list: """ @@ -5549,22 +7024,30 @@ class KeycloakAdmin: return self.__fetch_all(url, query) - def get_client_sessions_stats(self) -> dict: + def get_client_sessions_stats(self) -> list: """ Get current session count for all clients with active sessions. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientsessionstats :return: Dict of clients and session count - :rtype: dict + :rtype: list """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_SESSION_STATS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def get_client_management_permissions(self, client_id: str) -> list: + return res + + def get_client_management_permissions(self, client_id: str) -> dict: """ Get management permissions for a client. @@ -5572,15 +7055,23 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation :type client_id: str :return: Keycloak server response - :rtype: list + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_client_management_permissions(self, payload: dict, client_id: str) -> bytes: + def update_client_management_permissions(self, payload: dict, client_id: str) -> dict: """ Update management permissions for a client. @@ -5599,14 +7090,22 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation :type client_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_policy_scopes(self, client_id: str, policy_id: str) -> list: """ @@ -5628,7 +7127,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_policy_resources(self, client_id: str, policy_id: str) -> list: """ @@ -5650,9 +7157,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def get_client_authz_scope_permission(self, client_id: str, scope_id: str) -> list: + return res + + def get_client_authz_scope_permission(self, client_id: str, scope_id: str) -> dict: """ Get permissions for a given scope. @@ -5662,7 +7177,7 @@ class KeycloakAdmin: :param scope_id: No Document :type scope_id: str :return: Keycloak server response - :rtype: list + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -5672,9 +7187,17 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def create_client_authz_scope_permission(self, payload: dict, client_id: str) -> bytes: + def create_client_authz_scope_permission(self, payload: dict, client_id: str) -> dict: """ Create permissions for a authz scope. @@ -5696,7 +7219,7 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation :type client_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_post( @@ -5704,11 +7227,19 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def update_client_authz_scope_permission( self, @@ -5751,7 +7282,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) + res = raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def update_client_authz_resource_permission( self, @@ -5794,7 +7333,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_PERMISSION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) + res = raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_client_policies(self, client_id: str) -> list: """ @@ -5810,7 +7357,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_client_authz_permission_associated_policies( self, @@ -5839,9 +7394,17 @@ class KeycloakAdmin: **params_path, ), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def create_client_authz_client_policy(self, payload: dict, client_id: str) -> bytes: + def create_client_authz_client_policy(self, payload: dict, client_id: str) -> dict: """ Create a new policy for a given client. @@ -5861,18 +7424,26 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation :type client_id: str :return: Keycloak server response (RoleRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_composite_client_roles_of_group( self, @@ -5902,7 +7473,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_role_client_level_children(self, client_id: str, role_id: str) -> list: """ @@ -5923,7 +7502,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_ROLE_CHILDREN.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def upload_certificate(self, client_id: str, certcont: str) -> dict: """ @@ -5943,7 +7530,8 @@ class KeycloakAdmin: "attr": "jwt.credential", } m = MultipartEncoder(fields={"keystoreFormat": "Certificate PEM", "file": certcont}) - new_headers = copy.deepcopy(self.connection.headers) + orig_headers = copy.deepcopy(self.connection.headers or {}) + new_headers = copy.deepcopy(orig_headers) new_headers["Content-Type"] = m.content_type self.connection.headers = new_headers data_raw = self.connection.raw_post( @@ -5951,7 +7539,16 @@ class KeycloakAdmin: data=m, headers=new_headers, ) - return raise_error_from_response(data_raw, KeycloakPostError) + self.connection.headers = orig_headers + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_required_action_by_alias(self, action_alias: str) -> dict | None: """ @@ -5979,7 +7576,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_REQUIRED_ACTIONS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def update_required_action(self, action_alias: str, payload: dict) -> dict: """ @@ -5992,14 +7597,20 @@ class KeycloakAdmin: :return: empty dictionary. :rtype: dict """ - if not isinstance(payload, str): - payload = json.dumps(payload) params_path = {"realm-name": self.connection.realm_name, "action-alias": action_alias} data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_REQUIRED_ACTIONS_ALIAS.format(**params_path), - data=payload, + data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def get_bruteforce_detection_status(self, user_id: str) -> dict: """ @@ -6014,7 +7625,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def clear_bruteforce_attempts_for_user(self, user_id: str) -> dict: """ @@ -6029,7 +7648,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def clear_all_bruteforce_attempts(self) -> dict: """ @@ -6042,7 +7669,15 @@ class KeycloakAdmin: data_raw = self.connection.raw_delete( urls_patterns.URL_ADMIN_ATTACK_DETECTION.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def clear_keys_cache(self) -> dict: """ @@ -6056,11 +7691,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLEAR_KEYS_CACHE.format(**params_path), data="", ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def clear_realm_cache(self) -> dict: """ @@ -6074,11 +7717,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLEAR_REALM_CACHE.format(**params_path), data="", ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def clear_user_cache(self) -> dict: """ @@ -6092,11 +7743,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLEAR_USER_CACHE.format(**params_path), data="", ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res # async functions start async def a___fetch_all(self, url: str, query: dict | None = None) -> list: @@ -6148,12 +7807,20 @@ class KeycloakAdmin: :rtype: list """ query = query or {} - return raise_error_from_response( + res = raise_error_from_response( await self.connection.a_raw_get(url, **query), KeycloakGetError, ) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_get_current_realm(self) -> str: + return res + + async def a_get_current_realm(self) -> str | None: """ Return the currently configured realm asynchronously. @@ -6171,7 +7838,7 @@ class KeycloakAdmin: """ self.connection.realm_name = realm_name - async def a_import_realm(self, payload: dict) -> dict: + async def a_import_realm(self, payload: dict) -> bytes: """ Import a new realm asynchronously from a RealmRepresentation. @@ -6183,17 +7850,25 @@ class KeycloakAdmin: :param payload: RealmRepresentation :type payload: dict :return: RealmRepresentation - :rtype: dict + :rtype: bytes """ data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_partial_import_realm(self, realm_name: str, payload: dict) -> dict: """ @@ -6217,7 +7892,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_PARTIAL_IMPORT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_export_realm( self, @@ -6249,7 +7932,15 @@ class KeycloakAdmin: exportClients=export_clients, exportGroupsAndRoles=export_groups_and_role, ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_realms(self) -> list: """ @@ -6259,7 +7950,15 @@ class KeycloakAdmin: :rtype: list """ data_raw = await self.connection.a_raw_get(urls_patterns.URL_ADMIN_REALMS) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_realm(self, realm_name: str) -> dict: """ @@ -6277,9 +7976,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_REALM.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_create_realm(self, payload: dict, skip_exists: bool = False) -> dict: + async def a_create_realm(self, payload: dict, skip_exists: bool = False) -> bytes: """ Create a realm asynchronously. @@ -6291,18 +7998,33 @@ class KeycloakAdmin: :param skip_exists: Skip if Realm already exist. :type skip_exists: bool :return: Keycloak server response (RealmRepresentation) - :rtype: dict + :rtype: bytes """ data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED] + ([HTTP_BAD_REQUEST, HTTP_CONFLICT] if skip_exists else []), ) + if isinstance(res, dict) and res in [ + {"msg": "Already exists"}, + {"errorMessage": "Realm test already exists"}, + {"errorMessage": "Conflict detected. See logs for details"}, + ]: + return json.dumps(res).encode() + + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_update_realm(self, realm_name: str, payload: dict) -> dict: """ @@ -6326,11 +8048,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_update_realm_users_profile(self, payload: dict) -> dict: """ @@ -6346,30 +8076,46 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_USER_PROFILE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_OK], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_realm(self, realm_name: str) -> bytes: + return res + + async def a_delete_realm(self, realm_name: str) -> dict: """ Delete a realm asynchronously. :param realm_name: Realm name (not the realm id) :type realm_name: str :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": realm_name} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_REALM.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_users(self, query: dict | None = None) -> list: """ @@ -6394,7 +8140,7 @@ class KeycloakAdmin: return await self.a___fetch_all(url, query) - async def a_create_idp(self, payload: dict) -> dict: + async def a_create_idp(self, payload: dict) -> bytes: """ Create an ID Provider asynchronously. @@ -6404,20 +8150,28 @@ class KeycloakAdmin: :param: payload: IdentityProviderRepresentation :type payload: dict :returns: Keycloak server response - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_IDPS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_update_idp(self, idp_alias: str, payload: dict) -> bytes: + async def a_update_idp(self, idp_alias: str, payload: dict) -> dict: """ Update an ID Provider asynchronously. @@ -6429,20 +8183,28 @@ class KeycloakAdmin: :param: payload: The IdentityProviderRepresentation :type payload: dict :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_IDP.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_add_mapper_to_idp(self, idp_alias: str, payload: dict) -> dict: + async def a_add_mapper_to_idp(self, idp_alias: str, payload: dict) -> bytes: """ Create an ID Provider asynchronously. @@ -6454,20 +8216,28 @@ class KeycloakAdmin: :param: payload: IdentityProviderMapperRepresentation :type payload: dict :returns: Keycloak server response - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name, "idp-alias": idp_alias} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_update_mapper_in_idp(self, idp_alias: str, mapper_id: str, payload: dict) -> bytes: + return res + + async def a_update_mapper_in_idp(self, idp_alias: str, mapper_id: str, payload: dict) -> dict: """ Update an IdP mapper asynchronously. @@ -6481,7 +8251,7 @@ class KeycloakAdmin: :param: payload: IdentityProviderMapperRepresentation :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -6494,11 +8264,19 @@ class KeycloakAdmin: data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_idp_mappers(self, idp_alias: str) -> list: """ @@ -6518,7 +8296,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_idps(self) -> list: """ @@ -6536,7 +8322,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_IDPS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_idp(self, idp_alias: str) -> dict: """ @@ -6556,7 +8350,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_IDP.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_delete_idp(self, idp_alias: str) -> dict: """ @@ -6571,11 +8373,19 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_IDP.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_user(self, payload: dict, exist_ok: bool = False) -> str: """ @@ -6629,9 +8439,16 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USERS_COUNT.format(**params_path), **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, int): + msg = ( + f"Unexpected response type. Expected 'int', received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_get_user_id(self, username: str) -> str: + return res + + async def a_get_user_id(self, username: str) -> str | None: """ Get internal keycloak user id from username asynchronously. @@ -6670,7 +8487,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER.format(**params_path), userProfileMetadata=user_profile_metadata, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_user_groups( self, @@ -6703,7 +8528,7 @@ class KeycloakAdmin: return await self.a___fetch_all(url, query) - async def a_update_user(self, user_id: str, payload: dict) -> bytes: + async def a_update_user(self, user_id: str, payload: dict) -> dict: """ Update the user asynchronously. @@ -6713,20 +8538,28 @@ class KeycloakAdmin: :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_USER.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_disable_user(self, user_id: str) -> bytes: + return res + + async def a_disable_user(self, user_id: str) -> dict: """ Disable the user asynchronously from the realm. Disabled users can not log in. @@ -6738,7 +8571,7 @@ class KeycloakAdmin: """ return await self.a_update_user(user_id=user_id, payload={"enabled": False}) - async def a_enable_user(self, user_id: str) -> bytes: + async def a_enable_user(self, user_id: str) -> dict: """ Enable the user from the realm asynchronously. @@ -6764,31 +8597,39 @@ class KeycloakAdmin: user_id = user["id"] await self.a_enable_user(user_id=user_id) - async def a_delete_user(self, user_id: str) -> bytes: + async def a_delete_user(self, user_id: str) -> dict: """ Delete the user asynchronously. :param user_id: User id :type user_id: str :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_USER.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_set_user_password( self, user_id: str, password: str, temporary: bool = True, - ) -> bytes: + ) -> dict: """ Set up a password for the user asynchronously. @@ -6805,7 +8646,7 @@ class KeycloakAdmin: :param temporary: True if password is temporary :type temporary: bool :returns: Response - :rtype: bytes + :rtype: dict """ payload = {"type": "password", "temporary": temporary, "value": password} params_path = {"realm-name": self.connection.realm_name, "id": user_id} @@ -6813,11 +8654,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_RESET_PASSWORD.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_credentials(self, user_id: str) -> list: """ @@ -6837,9 +8686,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_USER_CREDENTIALS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_credential(self, user_id: str, credential_id: str) -> bytes: + async def a_delete_credential(self, user_id: str, credential_id: str) -> dict: """ Delete credential of the user asynchronously. @@ -6851,7 +8708,7 @@ class KeycloakAdmin: :param: credential_id: credential id :type credential_id: str :return: Keycloak server response (ClientRepresentation) - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -6861,9 +8718,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_USER_CREDENTIAL.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_user_logout(self, user_id: str) -> bytes: + return res + + async def a_user_logout(self, user_id: str) -> dict: """ Log out the user. @@ -6872,18 +8737,26 @@ class KeycloakAdmin: :param user_id: User id :type user_id: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_USER_LOGOUT.format(**params_path), data="", ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_user_consents(self, user_id: str) -> list: """ @@ -6901,9 +8774,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_USER_CONSENTS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_revoke_consent(self, user_id: str, client_id: str) -> dict | bytes: + return res + + async def a_revoke_consent(self, user_id: str, client_id: str) -> dict: """ Asynchronously revoke consent and offline tokens for particular client from user. @@ -6911,6 +8792,7 @@ class KeycloakAdmin: :type user_id: str :param client_id: Client id :type client_id: str + :rtype: dict """ params_path = { @@ -6921,11 +8803,19 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_USER_CONSENT.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_user_social_logins(self, user_id: str) -> list: """ @@ -6942,7 +8832,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_add_user_social_login( self, @@ -6950,7 +8848,7 @@ class KeycloakAdmin: provider_id: str, provider_userid: str, provider_username: str, - ) -> bytes: + ) -> dict: """ Add a federated identity / social login provider asynchronously to the user. @@ -6963,7 +8861,7 @@ class KeycloakAdmin: :param provider_username: username specified by the provider :type provider_username: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = { "identityProvider": provider_id, @@ -6979,13 +8877,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED, HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_user_social_login(self, user_id: str, provider_id: str) -> bytes: + async def a_delete_user_social_login(self, user_id: str, provider_id: str) -> dict: """ Delete a federated identity / social login provider asynchronously from the user. @@ -6994,7 +8900,7 @@ class KeycloakAdmin: :param provider_id: Social login provider id :type provider_id: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -7004,11 +8910,19 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_send_update_account( self, @@ -7017,7 +8931,7 @@ class KeycloakAdmin: client_id: str | None = None, lifespan: int | None = None, redirect_uri: str | None = None, - ) -> bytes: + ) -> dict: """ Send an update account email to the user asynchronously. @@ -7035,7 +8949,7 @@ class KeycloakAdmin: :type redirect_uri: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} params_query = {"client_id": client_id, "lifespan": lifespan, "redirect_uri": redirect_uri} @@ -7044,14 +8958,22 @@ class KeycloakAdmin: data=json.dumps(payload), **params_query, ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_send_verify_email( self, user_id: str, client_id: str | None = None, redirect_uri: str | None = None, - ) -> bytes: + ) -> dict: """ Send a update account email to the user asynchronously. @@ -7065,7 +8987,7 @@ class KeycloakAdmin: :type redirect_uri: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} params_query = {"client_id": client_id, "redirect_uri": redirect_uri} @@ -7074,7 +8996,15 @@ class KeycloakAdmin: data={}, **params_query, ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_sessions(self, user_id: str) -> list: """ @@ -7092,7 +9022,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_GET_SESSIONS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_server_info(self) -> dict: """ @@ -7105,7 +9043,15 @@ class KeycloakAdmin: :rtype: dict """ data_raw = await self.connection.a_raw_get(urls_patterns.URL_ADMIN_SERVER_INFO) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_groups(self, query: dict | None = None, full_hierarchy: bool = False) -> list: """ @@ -7170,7 +9116,7 @@ class KeycloakAdmin: ) if response.status_code >= HTTP_BAD_REQUEST: - return raise_error_from_response(response, KeycloakGetError) + raise_error_from_response(response, KeycloakGetError) # For version +23.0.0 group = response.json() @@ -7182,7 +9128,7 @@ class KeycloakAdmin: return group - async def a_get_subgroups(self, group: str, path: str) -> dict | None: + async def a_get_subgroups(self, group: dict, path: str) -> dict | None: """ Get subgroups asynchronously. @@ -7297,7 +9243,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, [HTTP_OK, 404]) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_group( self, @@ -7346,7 +9300,7 @@ class KeycloakAdmin: except KeyError: return None - async def a_update_group(self, group_id: str, payload: dict) -> bytes: + async def a_update_group(self, group_id: str, payload: dict) -> dict: """ Update group, ignores subgroups asynchronously. @@ -7366,11 +9320,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUP.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_groups_count(self, query: dict | None = None) -> dict: """ @@ -7390,9 +9352,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_COUNT.format(**params_path), **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_group_set_permissions(self, group_id: str, enabled: bool = True) -> bytes: + return res + + async def a_group_set_permissions(self, group_id: str, enabled: bool = True) -> dict: """ Enable/Disable permissions for a group asynchronously. @@ -7403,16 +9373,24 @@ class KeycloakAdmin: :param enabled: Enabled flag :type enabled: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_GROUP_PERMISSIONS.format(**params_path), data=json.dumps({"enabled": enabled}), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_group_user_add(self, user_id: str, group_id: str) -> bytes: + async def a_group_user_add(self, user_id: str, group_id: str) -> dict: """ Add user to group (user_id and group_id) asynchronously. @@ -7421,7 +9399,7 @@ class KeycloakAdmin: :param group_id: id of group to add to :type group_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -7432,13 +9410,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), data=None, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_group_user_remove(self, user_id: str, group_id: str) -> bytes: + async def a_group_user_remove(self, user_id: str, group_id: str) -> dict: """ Remove user from group (user_id and group_id) asynchronously. @@ -7447,7 +9433,7 @@ class KeycloakAdmin: :param group_id: id of group to remove from :type group_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -7457,30 +9443,46 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_group(self, group_id: str) -> bytes: + async def a_delete_group(self, group_id: str) -> dict: """ Delete a group in the Realm asynchronously. :param group_id: id of group to delete :type group_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_GROUP.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_clients(self) -> list: """ @@ -7498,7 +9500,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client(self, client_id: str) -> dict: """ @@ -7516,7 +9526,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_id(self, client_id: str) -> str | None: """ @@ -7536,6 +9554,12 @@ class KeycloakAdmin: clientId=client_id, ) data_response = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(data_response, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(data_response)}', value '{data_response}'." + ) + raise TypeError(msg) for client in data_response: if client_id == client.get("clientId"): @@ -7557,7 +9581,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_import_client_authz_config(self, client_id: str, payload: dict) -> dict: """ @@ -7572,7 +9604,8 @@ class KeycloakAdmin: :param payload: ResourceServerRepresentation :type payload: dict - :return: None + :return: Server response + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( @@ -7580,18 +9613,26 @@ class KeycloakAdmin: data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_client_authz_resource( self, client_id: str, payload: dict, skip_exists: bool = False, - ) -> bytes | dict: + ) -> dict: """ Create resources of client asynchronously. @@ -7605,7 +9646,7 @@ class KeycloakAdmin: :type skip_exists: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} @@ -7614,19 +9655,27 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_update_client_authz_resource( self, client_id: str, resource_id: str, payload: dict, - ) -> bytes: + ) -> dict: """ Update resource of client asynchronously. @@ -7647,7 +9696,7 @@ class KeycloakAdmin: :type resource_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -7658,13 +9707,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_client_authz_resource(self, client_id: str, resource_id: str) -> bytes: + return res + + async def a_delete_client_authz_resource(self, client_id: str, resource_id: str) -> dict: """ Delete a client resource asynchronously. @@ -7676,7 +9733,7 @@ class KeycloakAdmin: :type resource_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -7686,11 +9743,19 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_resources(self, client_id: str) -> list: """ @@ -7707,7 +9772,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path), max=-1, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_resource(self, client_id: str, resource_id: str) -> dict: """ @@ -7731,14 +9804,22 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_client_authz_role_based_policy( self, client_id: str, payload: dict, skip_exists: bool = False, - ) -> bytes: + ) -> dict: """ Create role-based policy of client asynchronously. @@ -7764,7 +9845,7 @@ class KeycloakAdmin: :param skip_exists: Skip creation in case the object exists :type skip_exists: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( @@ -7772,19 +9853,27 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_client_authz_policy( self, client_id: str, payload: dict, skip_exists: bool = False, - ) -> bytes: + ) -> dict: """ Create an authz policy of client asynchronously. @@ -7809,7 +9898,7 @@ class KeycloakAdmin: :param skip_exists: Skip creation in case the object exists :type skip_exists: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( @@ -7818,19 +9907,27 @@ class KeycloakAdmin: max=-1, permission=False, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_client_authz_resource_based_permission( self, client_id: str, payload: dict, skip_exists: bool = False, - ) -> bytes: + ) -> dict: """ Create resource-based permission of client asynchronously. @@ -7857,7 +9954,7 @@ class KeycloakAdmin: :param skip_exists: Skip creation in case the object already exists :type skip_exists: bool :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( @@ -7865,12 +9962,20 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_scopes(self, client_id: str) -> list: """ @@ -7887,9 +9992,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path), max=-1, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_create_client_authz_scopes(self, client_id: str, payload: dict) -> bytes: + async def a_create_client_authz_scopes(self, client_id: str, payload: dict) -> dict: """ Create scopes for client asynchronously. @@ -7900,7 +10013,7 @@ class KeycloakAdmin: :type payload: dict :type client_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( @@ -7908,11 +10021,19 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_permissions(self, client_id: str) -> list: """ @@ -7929,7 +10050,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_PERMISSIONS.format(**params_path), max=-1, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_policies(self, client_id: str) -> list: """ @@ -7947,9 +10076,17 @@ class KeycloakAdmin: max=-1, permission=False, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_client_authz_policy(self, client_id: str, policy_id: str) -> bytes: + async def a_delete_client_authz_policy(self, client_id: str, policy_id: str) -> dict: """ Delete a policy from client asynchronously. @@ -7970,11 +10107,19 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_policy(self, client_id: str, policy_id: str) -> dict: """ @@ -7997,7 +10142,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_service_account_user(self, client_id: str) -> dict: """ @@ -8013,7 +10166,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_default_client_scopes(self, client_id: str) -> list: """ @@ -8029,14 +10190,22 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_add_client_default_client_scope( self, client_id: str, client_scope_id: str, payload: dict, - ) -> bytes: + ) -> dict: """ Add a client scope to the default client scopes from client asynchronously. @@ -8056,7 +10225,7 @@ class KeycloakAdmin: :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8067,13 +10236,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_delete_client_default_client_scope( self, client_id: str, client_scope_id: str, - ) -> bytes: + ) -> dict: """ Delete a client scope from the default client scopes of the client asynchronously. @@ -8083,7 +10260,7 @@ class KeycloakAdmin: :type client_scope_id: str :return: list of client scopes with id and name - :rtype: list + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8093,7 +10270,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_optional_client_scopes(self, client_id: str) -> list: """ @@ -8109,14 +10294,22 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_add_client_optional_client_scope( self, client_id: str, client_scope_id: str, payload: dict, - ) -> bytes: + ) -> dict: """ Add a client scope to the optional client scopes from client asynchronously. @@ -8136,7 +10329,7 @@ class KeycloakAdmin: :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8147,13 +10340,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_delete_client_optional_client_scope( self, client_id: str, client_scope_id: str, - ) -> bytes: + ) -> dict: """ Delete a client scope from the optional client scopes of the client asynchronously. @@ -8163,7 +10364,7 @@ class KeycloakAdmin: :type client_scope_id: str :return: list of client scopes with id and name - :rtype: list + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8173,13 +10374,21 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_initial_access_token( self, count: int = 1, expiration: int = 1, - ) -> dict | bytes: + ) -> dict: """ Create an initial access token asynchronously. @@ -8196,7 +10405,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_INITIAL_ACCESS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_client(self, payload: dict, skip_exists: bool = False) -> str: """ @@ -8232,7 +10449,7 @@ class KeycloakAdmin: _last_slash_idx = data_raw.headers["Location"].rindex("/") return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_update_client(self, client_id: str, payload: dict) -> bytes: + async def a_update_client(self, client_id: str, payload: dict) -> dict: """ Update a client asynchronously. @@ -8242,20 +10459,28 @@ class KeycloakAdmin: :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_client(self, client_id: str) -> bytes: + async def a_delete_client(self, client_id: str) -> dict: """ Get representation of the client asynchronously. @@ -8265,19 +10490,27 @@ class KeycloakAdmin: :param client_id: keycloak client id (not oauth client-id) :type client_id: str :return: Keycloak server response (ClientRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_get_client_installation_provider(self, client_id: str, provider_id: str) -> list: + async def a_get_client_installation_provider(self, client_id: str, provider_id: str) -> dict: """ Get content for given installation provider asynchronously. @@ -8292,7 +10525,7 @@ class KeycloakAdmin: :param provider_id: provider id to specify response format :type provider_id: str :returns: Installation providers - :rtype: list + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8302,7 +10535,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_INSTALLATION_PROVIDER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_realm_users_profile(self) -> dict: """ @@ -8313,6 +10554,7 @@ class KeycloakAdmin: Return https://www.keycloak.org/docs-api/26.0.0/rest-api/index.html#UPConfig :returns: UPConfig + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} @@ -8320,7 +10562,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_REALM_USER_PROFILE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_realm_roles( self, brief_representation: bool = True, search_text: str = "", query: dict | None = None @@ -8342,7 +10592,7 @@ class KeycloakAdmin: """ query = query or {} params_path = {"realm-name": self.connection.realm_name} - params = {"briefRepresentation": brief_representation} + params: dict[str, str | bool] = {"briefRepresentation": brief_representation} url = urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path) if search_text is not None and search_text.strip() != "": @@ -8432,16 +10682,24 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES_REALM.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_remove_realm_default_roles(self, payload: dict) -> bytes: + return res + + async def a_remove_realm_default_roles(self, payload: list) -> dict: """ Remove a set of default realm roles asynchronously. :param payload: List of RoleRepresentations :type payload: list :return: Keycloak Server Response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8451,16 +10709,24 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_add_realm_default_roles(self, payload: dict) -> bytes: + async def a_add_realm_default_roles(self, payload: list) -> dict: """ Add a set of default realm roles asynchronously. :param payload: List of RoleRepresentations :type payload: list :return: Keycloak Server Response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8470,7 +10736,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_roles(self, client_id: str, brief_representation: bool = True) -> list: """ @@ -8492,7 +10766,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_role(self, client_id: str, role_name: str) -> dict: """ @@ -8513,9 +10795,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_get_client_role_id(self, client_id: str, role_name: str) -> str: + return res + + async def a_get_client_role_id(self, client_id: str, role_name: str) -> str | None: """ Get client role id by name asynchronously. @@ -8529,7 +10819,7 @@ class KeycloakAdmin: :param role_name: role's name (not id!) :type role_name: str :return: role_id - :rtype: str + :rtype: str | None """ role = await self.a_get_client_role(client_id, role_name) return role.get("id") @@ -8584,7 +10874,7 @@ class KeycloakAdmin: client_role_id: str, role_name: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Add composite roles to client role asynchronously. @@ -8595,7 +10885,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) to be updated :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -8607,18 +10897,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_remove_composite_client_roles_from_role( self, client_role_id: str, role_name: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Remove composite roles from a client role asynchronously. @@ -8629,7 +10927,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) to be removed :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -8641,13 +10939,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_update_client_role(self, client_id: str, role_name: str, payload: dict) -> bytes: + async def a_update_client_role(self, client_id: str, role_name: str, payload: dict) -> dict: """ Update a client role asynchronously. @@ -8661,7 +10967,7 @@ class KeycloakAdmin: :param payload: RoleRepresentation :type payload: dict :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8672,13 +10978,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_client_role(self, client_role_id: str, role_name: str) -> bytes: + return res + + async def a_delete_client_role(self, client_role_id: str, role_name: str) -> dict: """ Delete a client role asynchronously. @@ -8690,7 +11004,7 @@ class KeycloakAdmin: :param role_name: role's name (not id!) :type role_name: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -8700,13 +11014,21 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_assign_client_role(self, user_id: str, client_id: str, roles: str | list) -> bytes: + async def a_assign_client_role(self, user_id: str, client_id: str, roles: str | list) -> dict: """ Assign a client role to a user asynchronously. @@ -8717,7 +11039,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -8729,17 +11051,25 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_role_members( self, client_id: str, role_name: str, - **query: dict, + **query: Any, # noqa: ANN401 ) -> list: """ Get members by client role asynchronously. @@ -8768,7 +11098,7 @@ class KeycloakAdmin: self, client_id: str, role_name: str, - **query: dict, + **query: Any, # noqa: ANN401 ) -> list: """ Get group members by client role asynchronously. @@ -8809,9 +11139,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_update_role_by_id(self, role_id: str, payload: dict) -> bytes: + async def a_update_role_by_id(self, role_id: str, payload: dict) -> dict: """ Update the role asynchronously. @@ -8823,20 +11161,28 @@ class KeycloakAdmin: :param role_id: id of role :type role_id: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_role_by_id(self, role_id: str) -> bytes: + async def a_delete_role_by_id(self, role_id: str) -> dict: """ Delete a role by its id asynchronously. @@ -8852,11 +11198,19 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_role_composites_by_id(self, role_id: str, query: dict | None = None) -> list: """ @@ -8926,7 +11280,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_realm_role_by_id(self, role_id: str) -> dict: """ @@ -8944,9 +11306,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_update_realm_role(self, role_name: str, payload: dict) -> bytes: + async def a_update_realm_role(self, role_name: str, payload: dict) -> dict: """ Update a role for the realm by name asynchronously. @@ -8955,43 +11325,59 @@ class KeycloakAdmin: :param payload: The role (use RoleRepresentation) :type payload: dict :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_realm_role(self, role_name: str) -> bytes: + return res + + async def a_delete_realm_role(self, role_name: str) -> dict: """ Delete a role for the realm by name asynchronously. :param role_name: The role name :type role_name: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_add_composite_realm_roles_to_role( self, role_name: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Add composite roles to the role asynchronously. @@ -9000,7 +11386,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) to be updated :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} @@ -9008,17 +11394,25 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_remove_composite_realm_roles_to_role( self, role_name: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Remove composite roles from the role asynchronously. @@ -9027,7 +11421,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) to be removed :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} @@ -9035,11 +11429,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_composite_realm_roles_of_role(self, role_name: str) -> list: """ @@ -9054,7 +11456,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_composite_client_roles_of_role(self, client_id: str, role_name: str) -> list: """ @@ -9075,13 +11485,21 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_assign_realm_roles_to_client_scope( self, client_id: str, roles: str | list, - ) -> dict | bytes: + ) -> dict: """ Assign realm roles to a client's scope asynchronously. @@ -9098,17 +11516,25 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_delete_realm_roles_of_client_scope( self, client_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Delete realm roles of a client's scope asynchronously. @@ -9125,11 +11551,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_realm_roles_of_client_scope(self, client_id: str) -> list: """ @@ -9144,14 +11578,22 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_assign_client_roles_to_client_scope( self, client_id: str, client_roles_owner_id: str, roles: str | list, - ) -> dict | bytes: + ) -> dict: """ Assign client roles to a client's dedicated scope asynchronously. @@ -9176,18 +11618,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_delete_client_roles_of_client_scope( self, client_id: str, client_roles_owner_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Delete client roles of a client's dedicated scope asynchronously. @@ -9212,11 +11662,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_roles_of_client_scope( self, @@ -9243,9 +11701,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_assign_realm_roles(self, user_id: str, roles: str | list) -> bytes: + async def a_assign_realm_roles(self, user_id: str, roles: str | list) -> dict: """ Assign realm roles to a user asynchronously. @@ -9254,7 +11720,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": user_id} @@ -9262,13 +11728,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_realm_roles_of_user(self, user_id: str, roles: str | list) -> bytes: + async def a_delete_realm_roles_of_user(self, user_id: str, roles: str | list) -> dict: """ Delete realm roles of a user asynchronously. @@ -9277,7 +11751,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": user_id} @@ -9285,11 +11759,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_realm_roles_of_user(self, user_id: str) -> list: """ @@ -9304,7 +11786,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_available_realm_roles_of_user(self, user_id: str) -> list: """ @@ -9319,7 +11809,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_USER_REALM_ROLES_AVAILABLE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_composite_realm_roles_of_user( self, @@ -9342,9 +11840,17 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_assign_group_realm_roles(self, group_id: str, roles: str | list) -> bytes: + return res + + async def a_assign_group_realm_roles(self, group_id: str, roles: str | list) -> dict: """ Assign realm roles to a group asynchronously. @@ -9353,7 +11859,7 @@ class KeycloakAdmin: :param roles: roles list or role (use GroupRoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": group_id} @@ -9361,13 +11867,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_group_realm_roles(self, group_id: str, roles: str | list) -> bytes: + return res + + async def a_delete_group_realm_roles(self, group_id: str, roles: str | list) -> dict: """ Delete realm roles of a group asynchronously. @@ -9376,7 +11890,7 @@ class KeycloakAdmin: :param roles: roles list or role (use GroupRoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": group_id} @@ -9384,11 +11898,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_group_realm_roles( self, @@ -9411,14 +11933,22 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_assign_group_client_roles( self, group_id: str, client_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Assign client roles to a group asynchronously. @@ -9429,7 +11959,7 @@ class KeycloakAdmin: :param roles: roles list or role (use GroupRoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -9441,11 +11971,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_group_client_roles(self, group_id: str, client_id: str) -> list: """ @@ -9466,14 +12004,22 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_delete_group_client_roles( self, group_id: str, client_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Delete client roles of a group asynchronously. @@ -9484,7 +12030,7 @@ class KeycloakAdmin: :param roles: roles list or role (use GroupRoleRepresentation) :type roles: list :return: Keycloak server response (array RoleRepresentation) - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -9496,11 +12042,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_all_roles_of_user(self, user_id: str) -> dict: """ @@ -9515,7 +12069,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_USER_ALL_ROLES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_roles_of_user(self, user_id: str, client_id: str) -> list: """ @@ -9582,7 +12144,7 @@ class KeycloakAdmin: client_level_role_mapping_url: str, user_id: str, client_id: str, - **params: dict, + **params: Any, # noqa: ANN401 ) -> list: """ Get client roles of a single user helper asynchronously. @@ -9607,14 +12169,22 @@ class KeycloakAdmin: client_level_role_mapping_url.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_delete_client_roles_of_user( self, user_id: str, client_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Delete client roles from a user asynchronously. @@ -9625,7 +12195,7 @@ class KeycloakAdmin: :param roles: roles list or role to delete (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -9637,11 +12207,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_authentication_flows(self) -> list: """ @@ -9659,7 +12237,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_FLOWS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_authentication_flow_for_id(self, flow_id: str) -> dict: """ @@ -9679,7 +12265,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_FLOWS_ALIAS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_authentication_flow( self, @@ -9704,12 +12298,23 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if isinstance(res, dict) and res == {"msg": "Already exists"}: + return json.dumps(res).encode() + + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_copy_authentication_flow(self, payload: dict, flow_alias: str) -> bytes: """ @@ -9729,13 +12334,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_COPY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_authentication_flow(self, flow_id: str) -> bytes: + return res + + async def a_delete_authentication_flow(self, flow_id: str) -> dict: """ Delete authentication flow asynchronously. @@ -9745,17 +12358,25 @@ class KeycloakAdmin: :param flow_id: authentication flow id :type flow_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": flow_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_FLOW.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_authentication_flow_executions(self, flow_alias: str) -> list: """ @@ -9772,13 +12393,21 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_update_authentication_flow_executions( self, payload: dict, flow_alias: str, - ) -> bytes: + ) -> dict: """ Update an authentication flow execution asynchronously. @@ -9790,18 +12419,26 @@ class KeycloakAdmin: :param flow_alias: The flow alias :type flow_alias: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_ACCEPTED, HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_authentication_flow_execution(self, execution_id: str) -> dict: """ @@ -9819,7 +12456,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_authentication_flow_execution( self, @@ -9844,13 +12489,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_EXECUTION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_authentication_flow_execution(self, execution_id: str) -> bytes: + async def a_delete_authentication_flow_execution(self, execution_id: str) -> dict: """ Delete authentication flow execution asynchronously. @@ -9860,17 +12513,25 @@ class KeycloakAdmin: :param execution_id: keycloak client id (not oauth client-id) :type execution_id: str :return: Keycloak server response (json) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": execution_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_authentication_flow_subflow( self, @@ -9898,12 +12559,23 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_FLOW.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], skip_exists=skip_exists, ) + if isinstance(res, dict) and res == {"msg": "Already exists"}: + return json.dumps(res).encode() + + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_authenticator_providers(self) -> list: """ @@ -9916,7 +12588,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_AUTHENTICATOR_PROVIDERS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_authenticator_provider_config_description(self, provider_id: str) -> dict: """ @@ -9934,7 +12614,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG_DESCRIPTION.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_authenticator_config(self, config_id: str) -> dict: """ @@ -9951,9 +12639,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_update_authenticator_config(self, payload: dict, config_id: str) -> bytes: + async def a_update_authenticator_config(self, payload: dict, config_id: str) -> dict: """ Update an authenticator configuration asynchronously. @@ -9965,20 +12661,28 @@ class KeycloakAdmin: :param config_id: Authenticator config id :type config_id: str :return: Response(json) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": config_id} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_authenticator_config(self, config_id: str) -> bytes: + async def a_delete_authenticator_config(self, config_id: str) -> dict: """ Delete a authenticator configuration asynchronously. @@ -9987,19 +12691,27 @@ class KeycloakAdmin: :param config_id: Authenticator config id :type config_id: str :return: Keycloak server Response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": config_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_sync_users(self, storage_id: str, action: str) -> bytes: + return res + + async def a_sync_users(self, storage_id: str, action: str) -> dict: """ Trigger user sync from provider asynchronously. @@ -10008,7 +12720,7 @@ class KeycloakAdmin: :param action: Action can be "triggerFullSync" or "triggerChangedUsersSync" :type action: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ data = {"action": action} params_query = {"action": action} @@ -10019,7 +12731,15 @@ class KeycloakAdmin: data=json.dumps(data), **params_query, ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_scopes(self) -> list: """ @@ -10035,7 +12755,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_scope(self, client_scope_id: str) -> dict: """ @@ -10053,7 +12781,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_scope_by_name(self, client_scope_name: str) -> dict | None: """ @@ -10108,7 +12844,7 @@ class KeycloakAdmin: _last_slash_idx = data_raw.headers["Location"].rindex("/") return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_update_client_scope(self, client_scope_id: str, payload: dict) -> bytes: + async def a_update_client_scope(self, client_scope_id: str, payload: dict) -> dict: """ Update a client scope asynchronously. @@ -10120,20 +12856,28 @@ class KeycloakAdmin: :param payload: ClientScopeRepresentation :type payload: dict :return: Keycloak server response (ClientScopeRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_client_scope(self, client_scope_id: str) -> bytes: + async def a_delete_client_scope(self, client_scope_id: str) -> dict: """ Delete existing client scope asynchronously. @@ -10143,17 +12887,25 @@ class KeycloakAdmin: :param client_scope_id: The id of the client scope :type client_scope_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_mappers_from_client_scope(self, client_scope_id: str) -> list: """ @@ -10169,7 +12921,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_add_mapper_to_client_scope(self, client_scope_id: str, payload: dict) -> bytes: """ @@ -10189,17 +12949,25 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_delete_mapper_from_client_scope( self, client_scope_id: str, protocol_mapper_id: str, - ) -> bytes: + ) -> dict: """ Delete a mapper from a client scope asynchronously. @@ -10210,7 +12978,7 @@ class KeycloakAdmin: :param protocol_mapper_id: Protocol mapper id :type protocol_mapper_id: str :return: Keycloak server Response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -10220,18 +12988,26 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_update_mapper_in_client_scope( self, client_scope_id: str, protocol_mapper_id: str, payload: dict, - ) -> bytes: + ) -> dict: """ Update an existing protocol mapper in a client scope asynchronously. @@ -10245,7 +13021,7 @@ class KeycloakAdmin: :param payload: ProtocolMapperRepresentation :type payload: dict :return: Keycloak server Response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -10256,11 +13032,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_default_default_client_scopes(self) -> list: """ @@ -10275,35 +13059,51 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_delete_default_default_client_scope(self, scope_id: str) -> bytes: + async def a_delete_default_default_client_scope(self, scope_id: str) -> dict: """ Delete default default client scope asynchronously. :param scope_id: default default client scope id :type scope_id: str :return: Keycloak server response - :rtype: list + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_add_default_default_client_scope(self, scope_id: str) -> bytes: + return res + + async def a_add_default_default_client_scope(self, scope_id: str) -> dict: """ Add default default client scope asynchronously. :param scope_id: default default client scope id :type scope_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} payload = {"realm": self.connection.realm_name, "clientScopeId": scope_id} @@ -10311,11 +13111,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_default_optional_client_scopes(self) -> list: """ @@ -10330,35 +13138,51 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_default_optional_client_scope(self, scope_id: str) -> bytes: + return res + + async def a_delete_default_optional_client_scope(self, scope_id: str) -> dict: """ Delete default optional client scope asynchronously. :param scope_id: default optional client scope id :type scope_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_add_default_optional_client_scope(self, scope_id: str) -> bytes: + return res + + async def a_add_default_optional_client_scope(self, scope_id: str) -> dict: """ Add default optional client scope asynchronously. :param scope_id: default optional client scope id :type scope_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} payload = {"realm": self.connection.realm_name, "clientScopeId": scope_id} @@ -10366,18 +13190,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_add_client_specific_roles_to_client_scope( self, client_scope_id: str, client_roles_owner_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Assign client roles to a client scope asynchronously. @@ -10403,18 +13235,26 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_remove_client_specific_roles_of_client_scope( self, client_scope_id: str, client_roles_owner_id: str, roles: str | list, - ) -> bytes: + ) -> dict: """ Delete client roles of a client scope asynchronously. @@ -10428,7 +13268,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation, must include id and name) :type roles: list :return: Keycloak server response - :rtype: bytes + :rtype: dict """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -10440,11 +13280,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_specific_roles_of_client_scope( self, @@ -10472,9 +13320,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_get_all_roles_of_client_scope(self, client_scope_id: str) -> list: + return res + + async def a_get_all_roles_of_client_scope(self, client_scope_id: str) -> dict: """ Get all client roles for a client scope. @@ -10490,7 +13346,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_mappers_from_client(self, client_id: str) -> list: """ @@ -10508,7 +13372,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_add_mapper_to_client(self, client_id: str, payload: dict) -> bytes: """ @@ -10528,13 +13400,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_update_client_mapper(self, client_id: str, mapper_id: str, payload: dict) -> bytes: + return res + + async def a_update_client_mapper(self, client_id: str, mapper_id: str, payload: dict) -> dict: """ Update client mapper asynchronously. @@ -10545,7 +13425,7 @@ class KeycloakAdmin: :param payload: ProtocolMapperRepresentation :type payload: dict :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -10556,13 +13436,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_remove_client_mapper(self, client_id: str, client_mapper_id: str) -> bytes: + async def a_remove_client_mapper(self, client_id: str, client_mapper_id: str) -> dict: """ Remove a mapper from the client asynchronously. @@ -10573,7 +13461,7 @@ class KeycloakAdmin: :param client_mapper_id: The id of the mapper to be deleted :type client_mapper_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -10583,13 +13471,21 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_generate_client_secrets(self, client_id: str) -> bytes: + async def a_generate_client_secrets(self, client_id: str) -> dict: """ Generate a new secret for the client asynchronously. @@ -10598,14 +13494,22 @@ class KeycloakAdmin: :param client_id: id of client (not client-id) :type client_id: str :return: Keycloak server response (ClientRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), data=None, ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_secrets(self, client_id: str) -> dict: """ @@ -10622,7 +13526,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_components(self, query: dict | None = None) -> list: """ @@ -10645,7 +13557,15 @@ class KeycloakAdmin: data=None, **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_create_component(self, payload: dict) -> str: """ @@ -10668,7 +13588,7 @@ class KeycloakAdmin: _last_slash_idx = data_raw.headers["Location"].rindex("/") return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_get_component(self, component_id: str) -> dict | bytes: + async def a_get_component(self, component_id: str) -> dict: """ Get representation of the component asynchronously. @@ -10686,9 +13606,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_update_component(self, component_id: str, payload: dict) -> bytes: + async def a_update_component(self, component_id: str, payload: dict) -> dict: """ Update the component asynchronously. @@ -10698,39 +13626,55 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_componentrepresentation :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_delete_component(self, component_id: str) -> bytes: + return res + + async def a_delete_component(self, component_id: str) -> dict: """ Delete the component asynchronously. :param component_id: Component id :type component_id: str :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_get_keys(self) -> list: + async def a_get_keys(self) -> dict: """ Get keys asynchronously. @@ -10740,14 +13684,22 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_key_resource :return: keys list - :rtype: list + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_KEYS.format(**params_path), data=None, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_admin_events(self, query: dict | None = None) -> list: """ @@ -10771,7 +13723,15 @@ class KeycloakAdmin: data=None, **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_events(self, query: dict | None = None) -> list: """ @@ -10794,9 +13754,17 @@ class KeycloakAdmin: data=None, **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_set_events(self, payload: dict) -> bytes: + async def a_set_events(self, payload: dict) -> dict: """ Set realm events configuration asynchronously. @@ -10806,18 +13774,26 @@ class KeycloakAdmin: :param payload: Payload object for the events configuration :type payload: dict :return: Http response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_EVENTS_CONFIG.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_all_sessions(self, client_id: str, query: dict | None = None) -> list: """ @@ -10841,22 +13817,30 @@ class KeycloakAdmin: return await self.a___fetch_all(url, query) - async def a_get_client_sessions_stats(self) -> dict: + async def a_get_client_sessions_stats(self) -> list: """ Get current session count for all clients with active sessions asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientsessionstats :return: Dict of clients and session count - :rtype: dict + :rtype: list """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_SESSION_STATS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_get_client_management_permissions(self, client_id: str) -> list: + async def a_get_client_management_permissions(self, client_id: str) -> dict: """ Get management permissions for a client asynchronously. @@ -10864,15 +13848,23 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation :type client_id: str :return: Keycloak server response - :rtype: list + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_update_client_management_permissions(self, payload: dict, client_id: str) -> bytes: + return res + + async def a_update_client_management_permissions(self, payload: dict, client_id: str) -> dict: """ Update management permissions for a client asynchronously. @@ -10891,14 +13883,22 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation :type client_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_policy_scopes(self, client_id: str, policy_id: str) -> list: """ @@ -10920,7 +13920,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_policy_resources(self, client_id: str, policy_id: str) -> list: """ @@ -10942,9 +13950,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_get_client_authz_scope_permission(self, client_id: str, scope_id: str) -> list: + return res + + async def a_get_client_authz_scope_permission(self, client_id: str, scope_id: str) -> dict: """ Get permissions for a given scope asynchronously. @@ -10954,7 +13970,7 @@ class KeycloakAdmin: :param scope_id: No Document :type scope_id: str :return: Keycloak server response - :rtype: list + :rtype: dict """ params_path = { "realm-name": self.connection.realm_name, @@ -10964,9 +13980,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_create_client_authz_scope_permission(self, payload: dict, client_id: str) -> bytes: + return res + + async def a_create_client_authz_scope_permission(self, payload: dict, client_id: str) -> dict: """ Create permissions for a authz scope asynchronously. @@ -10988,7 +14012,7 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation :type client_id: str :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( @@ -10996,11 +14020,19 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_update_client_authz_scope_permission( self, @@ -11043,7 +14075,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) + res = raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_update_client_authz_resource_permission( self, @@ -11086,7 +14126,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_PERMISSION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) + res = raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_client_policies(self, client_id: str) -> list: """ @@ -11102,7 +14150,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_client_authz_permission_associated_policies( self, @@ -11131,9 +14187,17 @@ class KeycloakAdmin: **params_path, ), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_create_client_authz_client_policy(self, payload: dict, client_id: str) -> bytes: + async def a_create_client_authz_client_policy(self, payload: dict, client_id: str) -> dict: """ Create a new policy for a given client asynchronously. @@ -11153,18 +14217,26 @@ class KeycloakAdmin: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation :type client_id: str :return: Keycloak server response (RoleRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_composite_client_roles_of_group( self, @@ -11194,7 +14266,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), **params, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_role_client_level_children(self, client_id: str, role_id: str) -> list: """ @@ -11215,7 +14295,15 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_ROLE_CHILDREN.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_upload_certificate(self, client_id: str, certcont: str) -> dict: """ @@ -11235,7 +14323,8 @@ class KeycloakAdmin: "attr": "jwt.credential", } m = MultipartEncoder(fields={"keystoreFormat": "Certificate PEM", "file": certcont}) - new_headers = copy.deepcopy(self.connection.headers) + orig_headers = copy.deepcopy(self.connection.headers or {}) + new_headers = copy.deepcopy(orig_headers) new_headers["Content-Type"] = m.content_type self.connection.headers = new_headers data_raw = await self.connection.a_raw_post( @@ -11243,9 +14332,18 @@ class KeycloakAdmin: data=m, headers=new_headers, ) - return raise_error_from_response(data_raw, KeycloakPostError) + self.connection.headers = orig_headers + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_get_required_action_by_alias(self, action_alias: str) -> dict: + async def a_get_required_action_by_alias(self, action_alias: str) -> dict | None: """ Get a required action by its alias asynchronously. @@ -11271,9 +14369,17 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_REQUIRED_ACTIONS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_update_required_action(self, action_alias: str, payload: dict) -> bytes: + return res + + async def a_update_required_action(self, action_alias: str, payload: dict) -> dict: """ Update a required action asynchronously. @@ -11282,16 +14388,22 @@ class KeycloakAdmin: :param payload: the new required action (RequiredActionProviderRepresentation). :type payload: dict :return: empty dictionary. - :rtype: bytes + :rtype: dict """ - if not isinstance(payload, str): - payload = json.dumps(payload) params_path = {"realm-name": self.connection.realm_name, "action-alias": action_alias} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_REQUIRED_ACTIONS_ALIAS.format(**params_path), - data=payload, + data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_get_bruteforce_detection_status(self, user_id: str) -> dict: """ @@ -11306,89 +14418,137 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_clear_bruteforce_attempts_for_user(self, user_id: str) -> bytes: + async def a_clear_bruteforce_attempts_for_user(self, user_id: str) -> dict: """ Clear bruteforce attempts for user asynchronously. :param user_id: User id :type user_id: str :return: empty dictionary. - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_clear_all_bruteforce_attempts(self) -> bytes: + async def a_clear_all_bruteforce_attempts(self) -> dict: """ Clear bruteforce attempts for all users in realm asynchronously. :return: empty dictionary. - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_delete( urls_patterns.URL_ADMIN_ATTACK_DETECTION.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_clear_keys_cache(self) -> bytes: + return res + + async def a_clear_keys_cache(self) -> dict: """ Clear keys cache asynchronously. :return: empty dictionary. - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLEAR_KEYS_CACHE.format(**params_path), data="", ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_clear_realm_cache(self) -> bytes: + return res + + async def a_clear_realm_cache(self) -> dict: """ Clear realm cache asynchronously. :return: empty dictionary. - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLEAR_REALM_CACHE.format(**params_path), data="", ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_clear_user_cache(self) -> bytes: + return res + + async def a_clear_user_cache(self) -> dict: """ Clear user cache asynchronously. :return: empty dictionary. - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLEAR_USER_CACHE.format(**params_path), data="", ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_change_execution_priority(self, execution_id: str, diff: int) -> None: """ @@ -11441,20 +14601,28 @@ class KeycloakAdmin: :param payload: Configuration to add to the execution :type payload: dir :return: Response(json) - :rtype: dict + :rtype: bytes """ params_path = {"id": execution_id, "realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_FLOWS_EXECUTION_CONFIG.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_update_authentication_flow(self, flow_id: str, payload: dict) -> bytes: + async def a_update_authentication_flow(self, flow_id: str, payload: dict) -> dict: """ Update an authentication flow. @@ -11466,15 +14634,23 @@ class KeycloakAdmin: :param payload: AuthenticationFlowRepresentation :type payload: dict :return: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"id": flow_id, "realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_FLOW.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_ACCEPTED, HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res diff --git a/src/keycloak/keycloak_openid.py b/src/keycloak/keycloak_openid.py index e4ef560..e2257aa 100644 --- a/src/keycloak/keycloak_openid.py +++ b/src/keycloak/keycloak_openid.py @@ -31,6 +31,7 @@ from __future__ import annotations import json import pathlib +from typing import Any import aiofiles from jwcrypto import jwk, jwt @@ -100,7 +101,7 @@ class KeycloakOpenID: verify: bool | str = True, custom_headers: dict | None = None, proxies: dict | None = None, - timeout: int = 60, + timeout: int | None = 60, cert: str | tuple | None = None, max_retries: int = 1, pool_maxsize: int | None = None, @@ -166,7 +167,7 @@ class KeycloakOpenID: self._client_id = value @property - def client_secret_key(self) -> str: + def client_secret_key(self) -> str | None: """ Get the client secret key. @@ -176,7 +177,7 @@ class KeycloakOpenID: return self._client_secret_key @client_secret_key.setter - def client_secret_key(self, value: str) -> None: + def client_secret_key(self, value: str | None) -> None: self._client_secret_key = value @property @@ -246,7 +247,7 @@ class KeycloakOpenID: """ return self.client_id + "/" + role - def _token_info(self, token: str, method_token_info: str, **kwargs: dict) -> dict: + def _token_info(self, token: str, method_token_info: str, **kwargs: Any) -> dict: # noqa: ANN401 """ Getter for the token data. @@ -279,7 +280,15 @@ class KeycloakOpenID: """ params_path = {"realm-name": self.realm_name} data_raw = self.connection.raw_get(URL_WELL_KNOWN.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type on well_known. Expected 'dict', received '{type(res)}'" + f", value: {res}" + ) + raise TypeError(msg) + + return res def auth_url( self, @@ -325,15 +334,15 @@ class KeycloakOpenID: def token( self, - username: str = "", - password: str = "", + username: str | None = "", + password: str | None = "", grant_type: str = "password", code: str = "", redirect_uri: str = "", totp: int | None = None, scope: str = "openid", code_verifier: str | None = None, - **extra: dict, + **extra: Any, # noqa: ANN401 ) -> dict: """ Retrieve user token. @@ -385,7 +394,7 @@ class KeycloakOpenID: payload["totp"] = totp payload = self._add_secret_key(payload) - content_type = self.connection.headers.get("Content-Type") + content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload) ( @@ -393,7 +402,15 @@ class KeycloakOpenID: if content_type else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + f"Unexpected response type from 'token'. Expected 'dict', received '{type(res)}'" + f", value {res}." + ) + raise TypeError(msg) + + return res def refresh_token(self, refresh_token: str, grant_type: str = "refresh_token") -> dict: """ @@ -420,7 +437,7 @@ class KeycloakOpenID: "refresh_token": refresh_token, } payload = self._add_secret_key(payload) - content_type = self.connection.headers.get("Content-Type") + content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload) ( @@ -428,7 +445,16 @@ class KeycloakOpenID: if content_type else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type from refresh_token. " + f"Expected 'dict', received '{type(res)}'" + f", value: {res}." + ) + raise TypeError(msg) + + return res def exchange_token( self, @@ -480,7 +506,7 @@ class KeycloakOpenID: "scope": scope, } payload = self._add_secret_key(payload) - content_type = self.connection.headers.get("Content-Type") + content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload) ( @@ -488,7 +514,15 @@ class KeycloakOpenID: if content_type else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type from exchange_token. Expected 'dict', received " + f"'{type(res)}', value '{res}'" + ) + raise TypeError(msg) + + return res def userinfo(self, token: str) -> dict: """ @@ -504,7 +538,7 @@ class KeycloakOpenID: :returns: Userinfo object :rtype: dict """ - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) params_path = {"realm-name": self.realm_name} data_raw = self.connection.raw_get(URL_USERINFO.format(**params_path)) @@ -513,26 +547,42 @@ class KeycloakOpenID: if orig_bearer is not None else self.connection.del_param_headers("Authorization") ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type from userinfo. Expected 'dict', " + f"received '{type(res)}', value: '{res}'." + ) + raise TypeError(msg) - def logout(self, refresh_token: str) -> bytes: + return res + + def logout(self, refresh_token: str) -> dict: """ Log out the authenticated user. :param refresh_token: Refresh token from Keycloak :type refresh_token: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.realm_name} payload = {"client_id": self.client_id, "refresh_token": refresh_token} payload = self._add_secret_key(payload) data_raw = self.connection.raw_post(URL_LOGOUT.format(**params_path), data=payload) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type from logout. Expected 'dict', " + f"received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def certs(self) -> dict: """ @@ -549,7 +599,15 @@ class KeycloakOpenID: """ params_path = {"realm-name": self.realm_name} data_raw = self.connection.raw_get(URL_CERTS.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type from certs. Expected 'dict', " + f"received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def public_key(self) -> str: """ @@ -562,7 +620,15 @@ class KeycloakOpenID: """ params_path = {"realm-name": self.realm_name} data_raw = self.connection.raw_get(URL_REALM.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError)["public_key"] + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type from public_key. Expected 'dict', " + f"received '{type(res)}', value '{res}'" + ) + raise TypeError(msg) + + return res["public_key"] def entitlement(self, token: str, resource_server_id: str) -> dict: """ @@ -581,7 +647,7 @@ class KeycloakOpenID: :returns: Entitlements :rtype: dict """ - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id} data_raw = self.connection.raw_get(URL_ENTITLEMENT.format(**params_path)) @@ -592,9 +658,24 @@ class KeycloakOpenID: ) if data_raw.status_code in {HTTP_NOT_FOUND, HTTP_NOT_ALLOWED}: - return raise_error_from_response(data_raw, KeycloakDeprecationError) + res = raise_error_from_response(data_raw, KeycloakDeprecationError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', " + f"received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) + return res + + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', " + f"received '{type(res)}', value '{res}'." + ) + raise TypeError(msg) - return raise_error_from_response(data_raw, KeycloakGetError) # pragma: no cover + return res def introspect( self, @@ -629,7 +710,7 @@ class KeycloakOpenID: if token_type_hint == "requesting_party_token": # noqa: S105 if rpt: payload.update({"token": rpt, "token_type_hint": token_type_hint}) - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) bearer_changed = True else: @@ -645,10 +726,19 @@ class KeycloakOpenID: if orig_bearer is not None else self.connection.del_param_headers("Authorization") ) - return raise_error_from_response(data_raw, KeycloakPostError) + + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res @staticmethod - def _verify_token(token: str, key: jwk.JWK | jwk.JWKSet | None, **kwargs: dict) -> dict: + def _verify_token(token: str, key: jwk.JWK | jwk.JWKSet | None, **kwargs: Any) -> dict: # noqa: ANN401 """ Decode and optionally validate a token. @@ -669,12 +759,13 @@ class KeycloakOpenID: full_jwt = jwt.JWT(jwt=token, **kwargs) full_jwt.leeway = leeway full_jwt.validate(key) - return jwt.json_decode(full_jwt.claims) + return jwt.json_decode(full_jwt.claims) # pyright: ignore[reportAttributeAccessIssue] + full_jwt = jwt.JWT(jwt=token, **kwargs) full_jwt.token.objects["valid"] = True return json.loads(full_jwt.token.payload.decode("utf-8")) - def decode_token(self, token: str, validate: bool = True, **kwargs: dict) -> dict: + def decode_token(self, token: str, validate: bool = True, **kwargs: Any) -> dict: # noqa: ANN401 """ Decode user token. @@ -727,8 +818,8 @@ class KeycloakOpenID: self, token: str, method_token_info: str = "introspect", # noqa: S107 - **kwargs: dict, - ) -> list: + **kwargs: Any, # noqa: ANN401 + ) -> list | None: """ Get policies by user token. @@ -771,8 +862,8 @@ class KeycloakOpenID: self, token: str, method_token_info: str = "introspect", # noqa: S107 - **kwargs: dict, - ) -> list: + **kwargs: Any, # noqa: ANN401 + ) -> list | None: """ Get permission by user token. @@ -811,7 +902,7 @@ class KeycloakOpenID: return list(set(permissions)) - def uma_permissions(self, token: str, permissions: str = "", **extra_payload: dict) -> list: + def uma_permissions(self, token: str, permissions: str = "", **extra_payload: Any) -> list: # noqa: ANN401 """ Get UMA permissions by user token with requested permissions. @@ -840,9 +931,9 @@ class KeycloakOpenID: **extra_payload, } - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) - content_type = self.connection.headers.get("Content-Type") + content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload) ( @@ -855,9 +946,17 @@ class KeycloakOpenID: if orig_bearer is not None else self.connection.del_param_headers("Authorization") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def has_uma_access(self, token: str, permissions: list) -> AuthStatus: + return res + + def has_uma_access(self, token: str, permissions: str) -> AuthStatus: """ Determine whether user has uma permissions with specified user token. @@ -918,9 +1017,9 @@ class KeycloakOpenID: :rtype: dict """ params_path = {"realm-name": self.realm_name} - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) - orig_content_type = self.connection.headers.get("Content-Type") + orig_content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/json") data_raw = self.connection.raw_post( URL_CLIENT_REGISTRATION.format(**params_path), @@ -936,7 +1035,15 @@ class KeycloakOpenID: if orig_content_type is not None else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def device(self, scope: str = "") -> dict: """ @@ -964,9 +1071,17 @@ class KeycloakOpenID: payload = self._add_secret_key(payload) data_raw = self.connection.raw_post(URL_DEVICE.format(**params_path), data=payload) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def update_client(self, token: str, client_id: str, payload: dict) -> bytes: + def update_client(self, token: str, client_id: str, payload: dict) -> dict: """ Update a client. @@ -983,9 +1098,9 @@ class KeycloakOpenID: :rtype: bytes """ params_path = {"realm-name": self.realm_name, "client-id": client_id} - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) - orig_content_type = self.connection.headers.get("Content-Type") + orig_content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/json") # Keycloak complains if the clientId is not set in the payload @@ -1006,9 +1121,17 @@ class KeycloakOpenID: if orig_content_type is not None else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def _a_token_info(self, token: str, method_token_info: str, **kwargs: dict) -> dict: + async def _a_token_info(self, token: str, method_token_info: str, **kwargs: Any) -> dict: # noqa: ANN401 """ Asynchronous getter for the token data. @@ -1041,7 +1164,15 @@ class KeycloakOpenID: """ params_path = {"realm-name": self.realm_name} data_raw = await self.connection.a_raw_get(URL_WELL_KNOWN.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_auth_url( self, @@ -1087,15 +1218,15 @@ class KeycloakOpenID: async def a_token( self, - username: str = "", - password: str = "", + username: str | None = "", + password: str | None = "", grant_type: str = "password", code: str = "", redirect_uri: str = "", totp: int | None = None, scope: str = "openid", code_verifier: str | None = None, - **extra: dict, + **extra: Any, # noqa: ANN401 ) -> dict: """ Retrieve user token asynchronously. @@ -1147,7 +1278,7 @@ class KeycloakOpenID: payload["totp"] = totp payload = self._add_secret_key(payload) - content_type = self.connection.headers.get("Content-Type") + content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = await self.connection.a_raw_post(URL_TOKEN.format(**params_path), data=payload) ( @@ -1155,7 +1286,15 @@ class KeycloakOpenID: if content_type else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_refresh_token(self, refresh_token: str, grant_type: str = "refresh_token") -> dict: """ @@ -1182,7 +1321,7 @@ class KeycloakOpenID: "refresh_token": refresh_token, } payload = self._add_secret_key(payload) - content_type = self.connection.headers.get("Content-Type") + content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = await self.connection.a_raw_post(URL_TOKEN.format(**params_path), data=payload) ( @@ -1190,7 +1329,15 @@ class KeycloakOpenID: if content_type else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_exchange_token( self, @@ -1242,7 +1389,7 @@ class KeycloakOpenID: "scope": scope, } payload = self._add_secret_key(payload) - content_type = self.connection.headers.get("Content-Type") + content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = await self.connection.a_raw_post(URL_TOKEN.format(**params_path), data=payload) ( @@ -1250,7 +1397,15 @@ class KeycloakOpenID: if content_type else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_userinfo(self, token: str) -> dict: """ @@ -1266,7 +1421,7 @@ class KeycloakOpenID: :returns: Userinfo object :rtype: dict """ - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) params_path = {"realm-name": self.realm_name} data_raw = await self.connection.a_raw_get(URL_USERINFO.format(**params_path)) @@ -1275,26 +1430,42 @@ class KeycloakOpenID: if orig_bearer is not None else self.connection.del_param_headers("Authorization") ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_logout(self, refresh_token: str) -> bytes: + return res + + async def a_logout(self, refresh_token: str) -> dict: """ Log out the authenticated user asynchronously. :param refresh_token: Refresh token from Keycloak :type refresh_token: str :returns: Keycloak server response - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.realm_name} payload = {"client_id": self.client_id, "refresh_token": refresh_token} payload = self._add_secret_key(payload) data_raw = await self.connection.a_raw_post(URL_LOGOUT.format(**params_path), data=payload) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_certs(self) -> dict: """ @@ -1311,7 +1482,15 @@ class KeycloakOpenID: """ params_path = {"realm-name": self.realm_name} data_raw = await self.connection.a_raw_get(URL_CERTS.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_public_key(self) -> str: """ @@ -1324,7 +1503,15 @@ class KeycloakOpenID: """ params_path = {"realm-name": self.realm_name} data_raw = await self.connection.a_raw_get(URL_REALM.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError)["public_key"] + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res["public_key"] async def a_entitlement(self, token: str, resource_server_id: str) -> dict: """ @@ -1343,7 +1530,7 @@ class KeycloakOpenID: :returns: Entitlements :rtype: dict """ - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id} data_raw = await self.connection.a_raw_get(URL_ENTITLEMENT.format(**params_path)) @@ -1354,9 +1541,25 @@ class KeycloakOpenID: ) if data_raw.status_code in [HTTP_NOT_FOUND, HTTP_NOT_ALLOWED]: - return raise_error_from_response(data_raw, KeycloakDeprecationError) + res = raise_error_from_response(data_raw, KeycloakDeprecationError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - return raise_error_from_response(data_raw, KeycloakGetError) # pragma: no cover + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_introspect( self, @@ -1391,7 +1594,7 @@ class KeycloakOpenID: if token_type_hint == "requesting_party_token": # noqa: S105 if rpt: payload.update({"token": rpt, "token_type_hint": token_type_hint}) - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) bearer_changed = True else: @@ -1410,9 +1613,17 @@ class KeycloakOpenID: if orig_bearer is not None else self.connection.del_param_headers("Authorization") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_decode_token(self, token: str, validate: bool = True, **kwargs: dict) -> dict: + return res + + async def a_decode_token(self, token: str, validate: bool = True, **kwargs: Any) -> dict: # noqa: ANN401 """ Decode user token asynchronously. @@ -1465,8 +1676,8 @@ class KeycloakOpenID: self, token: str, method_token_info: str = "introspect", # noqa: S107 - **kwargs: dict, - ) -> list: + **kwargs: Any, # noqa: ANN401 + ) -> list | None: """ Get policies by user token asynchronously. @@ -1477,7 +1688,7 @@ class KeycloakOpenID: :param kwargs: Additional keyword arguments :type kwargs: dict :return: Policies - :rtype: list + :rtype: list | None :raises KeycloakAuthorizationConfigError: In case of bad authorization configuration :raises KeycloakInvalidTokenError: In case of bad token """ @@ -1509,8 +1720,8 @@ class KeycloakOpenID: self, token: str, method_token_info: str = "introspect", # noqa: S107 - **kwargs: dict, - ) -> list: + **kwargs: Any, # noqa: ANN401 + ) -> list | None: """ Get permission by user token asynchronously. @@ -1521,7 +1732,7 @@ class KeycloakOpenID: :param kwargs: parameters for decode :type kwargs: dict :returns: permissions list - :rtype: list + :rtype: list | None :raises KeycloakAuthorizationConfigError: In case of bad authorization configuration :raises KeycloakInvalidTokenError: In case of bad token """ @@ -1552,7 +1763,7 @@ class KeycloakOpenID: self, token: str, permissions: str = "", - **extra_payload: dict, + **extra_payload: Any, # noqa: ANN401 ) -> list: """ Get UMA permissions by user token with requested permissions asynchronously. @@ -1582,9 +1793,9 @@ class KeycloakOpenID: **extra_payload, } - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) - content_type = self.connection.headers.get("Content-Type") + content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = await self.connection.a_raw_post(URL_TOKEN.format(**params_path), data=payload) ( @@ -1597,9 +1808,17 @@ class KeycloakOpenID: if orig_bearer is not None else self.connection.del_param_headers("Authorization") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_has_uma_access(self, token: str, permissions: list) -> AuthStatus: + return res + + async def a_has_uma_access(self, token: str, permissions: str) -> AuthStatus: """ Determine whether user has uma permissions with specified user token asynchronously. @@ -1660,9 +1879,9 @@ class KeycloakOpenID: :rtype: dict """ params_path = {"realm-name": self.realm_name} - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) - orig_content_type = self.connection.headers.get("Content-Type") + orig_content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/json") data_raw = await self.connection.a_raw_post( URL_CLIENT_REGISTRATION.format(**params_path), @@ -1678,7 +1897,15 @@ class KeycloakOpenID: if orig_content_type is not None else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_device(self, scope: str = "") -> dict: """ @@ -1706,9 +1933,17 @@ class KeycloakOpenID: payload = self._add_secret_key(payload) data_raw = await self.connection.a_raw_post(URL_DEVICE.format(**params_path), data=payload) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_update_client(self, token: str, client_id: str, payload: dict) -> bytes: + async def a_update_client(self, token: str, client_id: str, payload: dict) -> dict: """ Update a client asynchronously. @@ -1722,12 +1957,12 @@ class KeycloakOpenID: :param payload: ClientRepresentation :type payload: dict :return: Client Representation - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.realm_name, "client-id": client_id} - orig_bearer = self.connection.headers.get("Authorization") + orig_bearer = (self.connection.headers or {}).get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) - orig_content_type = self.connection.headers.get("Content-Type") + orig_content_type = (self.connection.headers or {}).get("Content-Type") self.connection.add_param_headers("Content-Type", "application/json") # Keycloak complains if the clientId is not set in the payload @@ -1748,4 +1983,12 @@ class KeycloakOpenID: if orig_content_type is not None else self.connection.del_param_headers("Content-Type") ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res diff --git a/src/keycloak/keycloak_uma.py b/src/keycloak/keycloak_uma.py index 511e68e..6489856 100644 --- a/src/keycloak/keycloak_uma.py +++ b/src/keycloak/keycloak_uma.py @@ -30,11 +30,9 @@ https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-federated-authz-2.0.html from __future__ import annotations import json -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from urllib.parse import quote_plus -from async_property import async_property - from .connection import ConnectionManager from .exceptions import ( HTTP_CREATED, @@ -49,7 +47,7 @@ from .exceptions import ( from .urls_patterns import URL_UMA_WELL_KNOWN if TYPE_CHECKING: - from collections.abc import Iterable + from collections.abc import AsyncGenerator, Generator, Iterable from .openid_connection import KeycloakOpenIDConnection from .uma_permissions import UMAPermission @@ -75,10 +73,18 @@ class KeycloakUMA: def _fetch_well_known(self) -> dict: params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get(URL_UMA_WELL_KNOWN.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res @staticmethod - def format_url(url: str, **kwargs: dict) -> str: + def format_url(url: str, **kwargs: Any) -> str: # noqa: ANN401 """ Substitute url path parameters. @@ -97,7 +103,7 @@ class KeycloakUMA: return url.format(**{k: quote_plus(v) for k, v in kwargs.items()}) @staticmethod - async def a_format_url(url: str, **kwargs: dict) -> str: + async def a_format_url(url: str, **kwargs: Any) -> str: # noqa: ANN401 """ Substitute url path parameters. @@ -129,7 +135,7 @@ class KeycloakUMA: return self._well_known - @async_property + @property async def a_uma_well_known(self) -> dict: """ Get the well_known UMA2 config async. @@ -142,7 +148,7 @@ class KeycloakUMA: return self._well_known - def resource_set_create(self, payload: dict) -> dict | bytes: + def resource_set_create(self, payload: dict) -> dict: """ Create a resource set. @@ -161,13 +167,21 @@ class KeycloakUMA: self.uma_well_known["resource_registration_endpoint"], data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def resource_set_update(self, resource_id: str, payload: dict) -> bytes: + return res + + def resource_set_update(self, resource_id: str, payload: dict) -> dict: """ Update a resource set. @@ -189,11 +203,19 @@ class KeycloakUMA: id=resource_id, ) data_raw = self.connection.raw_put(url, data=json.dumps(payload)) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def resource_set_read(self, resource_id: str) -> dict: """ @@ -215,9 +237,17 @@ class KeycloakUMA: id=resource_id, ) data_raw = self.connection.raw_get(url) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - def resource_set_delete(self, resource_id: str) -> bytes: + return res + + def resource_set_delete(self, resource_id: str) -> dict: """ Delete a resource set. @@ -234,11 +264,19 @@ class KeycloakUMA: id=resource_id, ) data_raw = self.connection.raw_delete(url) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def resource_set_list_ids( self, @@ -303,9 +341,17 @@ class KeycloakUMA: self.uma_well_known["resource_registration_endpoint"], **query, ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def resource_set_list(self) -> list: + def resource_set_list(self) -> Generator[dict, Any, Any]: """ List all resource sets. @@ -363,13 +409,21 @@ class KeycloakUMA: self.uma_well_known["permission_endpoint"], data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def permissions_check( self, token: str, permissions: Iterable[UMAPermission], - **extra_payload: dict, + **extra_payload: Any, # noqa: ANN401 ) -> bool: """ Check UMA permissions by user token with requested permissions. @@ -401,6 +455,12 @@ class KeycloakUMA: if len(payload["permission"]) == 0: return True + if self.connection.base_url is None: + msg = ( + "Unable to perform permission check without base_url set on the connection object." + ) + raise AttributeError(msg) + connection = ConnectionManager(self.connection.base_url) connection.add_param_headers("Authorization", "Bearer " + token) connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") @@ -409,6 +469,14 @@ class KeycloakUMA: data = raise_error_from_response(data_raw, KeycloakPostError) except KeycloakPostError: return False + + if not isinstance(data, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(data)}', value '{data}'." + ) + raise TypeError(msg) + return data.get("result", False) def policy_resource_create(self, resource_id: str, payload: dict) -> dict: @@ -430,9 +498,17 @@ class KeycloakUMA: self.uma_well_known["policy_endpoint"] + f"/{resource_id}", data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - def policy_update(self, policy_id: str, payload: dict) -> dict: + def policy_update(self, policy_id: str, payload: dict) -> bytes: """ Update permission policy. @@ -444,13 +520,21 @@ class KeycloakUMA: :param payload: policy permission configuration :type payload: dict :return: PermissionRepresentation - :rtype: dict + :rtype: bytes """ data_raw = self.connection.raw_put( self.uma_well_known["policy_endpoint"] + f"/{policy_id}", data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def policy_delete(self, policy_id: str) -> dict: """ @@ -467,7 +551,15 @@ class KeycloakUMA: data_raw = self.connection.raw_delete( self.uma_well_known["policy_endpoint"] + f"/{policy_id}", ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res def policy_query( self, @@ -509,7 +601,18 @@ class KeycloakUMA: query["max"] = maximum data_raw = self.connection.raw_get(self.uma_well_known["policy_endpoint"], **query) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if isinstance(res, dict) and res == {}: + return [] + + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a__fetch_well_known(self) -> dict: """ @@ -520,7 +623,15 @@ class KeycloakUMA: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get(URL_UMA_WELL_KNOWN.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_resource_set_create(self, payload: dict) -> dict: """ @@ -541,13 +652,21 @@ class KeycloakUMA: (await self.a_uma_well_known)["resource_registration_endpoint"], data=json.dumps(payload), ) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_resource_set_update(self, resource_id: str, payload: dict) -> bytes: + async def a_resource_set_update(self, resource_id: str, payload: dict) -> dict: """ Update a resource set asynchronously. @@ -562,18 +681,26 @@ class KeycloakUMA: :param payload: ResourceRepresentation :type payload: dict :return: Response dict (empty) - :rtype: bytes + :rtype: dict """ url = self.format_url( (await self.a_uma_well_known)["resource_registration_endpoint"] + "/{id}", id=resource_id, ) data_raw = await self.connection.a_raw_put(url, data=json.dumps(payload)) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakPutError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_resource_set_read(self, resource_id: str) -> dict: """ @@ -595,9 +722,17 @@ class KeycloakUMA: id=resource_id, ) data_raw = await self.connection.a_raw_get(url) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) - async def a_resource_set_delete(self, resource_id: str) -> bytes: + return res + + async def a_resource_set_delete(self, resource_id: str) -> dict: """ Delete a resource set asynchronously. @@ -607,18 +742,26 @@ class KeycloakUMA: :param resource_id: id of the resource :type resource_id: str :return: Response dict (empty) - :rtype: bytes + :rtype: dict """ url = self.format_url( (await self.a_uma_well_known)["resource_registration_endpoint"] + "/{id}", id=resource_id, ) data_raw = await self.connection.a_raw_delete(url) - return raise_error_from_response( + res = raise_error_from_response( data_raw, KeycloakDeleteError, expected_codes=[HTTP_NO_CONTENT], ) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_resource_set_list_ids( self, @@ -683,9 +826,17 @@ class KeycloakUMA: (await self.a_uma_well_known)["resource_registration_endpoint"], **query, ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + res = raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_resource_set_list(self) -> list: + async def a_resource_set_list(self) -> AsyncGenerator[dict, Any]: """ List all resource sets asynchronously. @@ -702,7 +853,7 @@ class KeycloakUMA: resource = await self.a_resource_set_read(resource_id) yield resource - async def a_permission_ticket_create(self, permissions: Iterable[UMAPermission]) -> bool: + async def a_permission_ticket_create(self, permissions: Iterable[UMAPermission]) -> dict: """ Create a permission ticket asynchronously. @@ -743,13 +894,21 @@ class KeycloakUMA: (await self.a_uma_well_known)["permission_endpoint"], data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_permissions_check( self, token: str, permissions: Iterable[UMAPermission], - **extra_payload: dict, + **extra_payload: Any, # noqa: ANN401 ) -> bool: """ Check UMA permissions by user token with requested permissions asynchronously. @@ -781,6 +940,12 @@ class KeycloakUMA: if len(payload["permission"]) == 0: return True + if self.connection.base_url is None: + msg = ( + "Unable to perform permission check without base_url set on the connection object." + ) + raise AttributeError(msg) + connection = ConnectionManager(self.connection.base_url) connection.add_param_headers("Authorization", "Bearer " + token) connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") @@ -792,6 +957,14 @@ class KeycloakUMA: data = raise_error_from_response(data_raw, KeycloakPostError) except KeycloakPostError: return False + + if not isinstance(data, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(data)}', value '{data}'." + ) + raise TypeError(msg) + return data.get("result", False) async def a_policy_resource_create(self, resource_id: str, payload: dict) -> dict: @@ -813,9 +986,17 @@ class KeycloakUMA: (await self.a_uma_well_known)["policy_endpoint"] + f"/{resource_id}", data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError) + res = raise_error_from_response(data_raw, KeycloakPostError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res - async def a_policy_update(self, policy_id: str, payload: dict) -> dict: + async def a_policy_update(self, policy_id: str, payload: dict) -> bytes: """ Update permission policy asynchronously. @@ -827,13 +1008,21 @@ class KeycloakUMA: :param payload: policy permission configuration :type payload: dict :return: PermissionRepresentation - :rtype: dict + :rtype: bytes """ data_raw = await self.connection.a_raw_put( (await self.a_uma_well_known)["policy_endpoint"] + f"/{policy_id}", data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError) + res = raise_error_from_response(data_raw, KeycloakPutError) + if not isinstance(res, bytes): + msg = ( + "Unexpected response type. Expected 'bytes', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_policy_delete(self, policy_id: str) -> dict: """ @@ -850,7 +1039,15 @@ class KeycloakUMA: data_raw = await self.connection.a_raw_delete( (await self.a_uma_well_known)["policy_endpoint"] + f"/{policy_id}", ) - return raise_error_from_response(data_raw, KeycloakDeleteError) + res = raise_error_from_response(data_raw, KeycloakDeleteError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res async def a_policy_query( self, @@ -895,4 +1092,15 @@ class KeycloakUMA: (await self.a_uma_well_known)["policy_endpoint"], **query, ) - return raise_error_from_response(data_raw, KeycloakGetError) + res = raise_error_from_response(data_raw, KeycloakGetError) + if isinstance(res, dict) and res == {}: + return [] + + if not isinstance(res, list): + msg = ( + "Unexpected response type. Expected 'list', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res diff --git a/src/keycloak/openid_connection.py b/src/keycloak/openid_connection.py index c22d376..a2a4299 100644 --- a/src/keycloak/openid_connection.py +++ b/src/keycloak/openid_connection.py @@ -31,7 +31,7 @@ of openid tokens when required. from __future__ import annotations from datetime import datetime, timedelta, timezone -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from httpx import Response as AsyncResponse @@ -67,13 +67,13 @@ class KeycloakOpenIDConnection(ConnectionManager): def __init__( self, - server_url: str, + server_url: str | None = None, grant_type: str | None = None, username: str | None = None, password: str | None = None, - token: str | None = None, - totp: str | None = None, - realm_name: str = "master", + token: dict | None = None, + totp: int | None = None, + realm_name: str | None = "master", client_id: str = "admin-cli", verify: str | bool = True, client_secret_key: str | None = None, @@ -150,6 +150,10 @@ class KeycloakOpenIDConnection(ConnectionManager): elif client_secret_key: self.grant_type = "client_credentials" + if self.server_url is None: + msg = "Unable to initialize KeycloakOpenIDConnection without server_url." + raise ValueError(msg) + super().__init__( base_url=self.server_url, headers=self.headers, @@ -161,7 +165,7 @@ class KeycloakOpenIDConnection(ConnectionManager): ) @property - def server_url(self) -> str: + def server_url(self) -> str | None: """ Get server url. @@ -171,11 +175,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self.base_url @server_url.setter - def server_url(self, value: str) -> None: + def server_url(self, value: str | None) -> None: self.base_url = value @property - def grant_type(self) -> str: + def grant_type(self) -> str | None: """ Get grant type. @@ -185,11 +189,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._grant_type @grant_type.setter - def grant_type(self, value: str) -> None: + def grant_type(self, value: str | None) -> None: self._grant_type = value @property - def realm_name(self) -> str: + def realm_name(self) -> str | None: """ Get realm name. @@ -199,11 +203,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._realm_name @realm_name.setter - def realm_name(self, value: str) -> None: + def realm_name(self, value: str | None) -> None: self._realm_name = value @property - def client_id(self) -> str: + def client_id(self) -> str | None: """ Get client id. @@ -213,11 +217,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._client_id @client_id.setter - def client_id(self, value: str) -> None: + def client_id(self, value: str | None) -> None: self._client_id = value @property - def client_secret_key(self) -> str: + def client_secret_key(self) -> str | None: """ Get client secret key. @@ -227,11 +231,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._client_secret_key @client_secret_key.setter - def client_secret_key(self, value: str) -> None: + def client_secret_key(self, value: str | None) -> None: self._client_secret_key = value @property - def username(self) -> str: + def username(self) -> str | None: """ Get username. @@ -241,11 +245,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._username @username.setter - def username(self, value: str) -> None: + def username(self, value: str | None) -> None: self._username = value @property - def password(self) -> str: + def password(self) -> str | None: """ Get password. @@ -255,11 +259,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._password @password.setter - def password(self, value: str) -> None: + def password(self, value: str | None) -> None: self._password = value @property - def totp(self) -> str: + def totp(self) -> int | None: """ Get totp. @@ -269,11 +273,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._totp @totp.setter - def totp(self, value: str) -> None: + def totp(self, value: int | None) -> None: self._totp = value @property - def token(self) -> dict: + def token(self) -> dict | None: """ Get token. @@ -283,16 +287,16 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._token @token.setter - def token(self, value: dict) -> None: + def token(self, value: dict | None) -> None: self._token = value self._expires_at = datetime.now(tz=timezone.utc) + timedelta( - seconds=int(self.token_lifetime_fraction * self.token["expires_in"] if value else 0), + seconds=int(self.token_lifetime_fraction * value["expires_in"] if value else 0), ) if value is not None: - self.add_param_headers("Authorization", "Bearer " + value.get("access_token")) + self.add_param_headers("Authorization", "Bearer " + value["access_token"]) @property - def expires_at(self) -> datetime: + def expires_at(self) -> datetime | None: """ Get token expiry time. @@ -302,7 +306,7 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._expires_at @property - def user_realm_name(self) -> str: + def user_realm_name(self) -> str | None: """ Get user realm name. @@ -312,11 +316,11 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._user_realm_name @user_realm_name.setter - def user_realm_name(self, value: str) -> None: + def user_realm_name(self, value: str | None) -> None: self._user_realm_name = value @property - def custom_headers(self) -> dict: + def custom_headers(self) -> dict | None: """ Get custom headers. @@ -326,9 +330,9 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._custom_headers @custom_headers.setter - def custom_headers(self, value: dict) -> None: + def custom_headers(self, value: dict | None) -> None: self._custom_headers = value - if self.custom_headers is not None: + if self.custom_headers is not None and self.headers is not None: # merge custom headers to main headers self.headers.update(self.custom_headers) @@ -350,6 +354,14 @@ class KeycloakOpenIDConnection(ConnectionManager): else: token_realm_name = "master" # noqa: S105 + if self.client_id is None: + msg = "Unable to get KeycloakOpenID client without client_id set." + raise AttributeError(msg) + + if self.server_url is None: + msg = "Unable to get KeycloakOpenID without server_url set." + raise AttributeError(msg) + self._keycloak_openid = KeycloakOpenID( server_url=self.server_url, client_id=self.client_id, @@ -398,17 +410,17 @@ class KeycloakOpenIDConnection(ConnectionManager): b"Session not active", ] if e.response_code == HTTP_BAD_REQUEST and any( - err in e.response_body for err in list_errors + err in (e.response_body or b"") for err in list_errors ): self.get_token() else: raise def _refresh_if_required(self) -> None: - if datetime.now(tz=timezone.utc) >= self.expires_at: + if self.expires_at is not None and datetime.now(tz=timezone.utc) >= self.expires_at: self.refresh_token() - def raw_get(self, *args: list, **kwargs: dict) -> Response: + def raw_get(self, *args: Any, **kwargs: Any) -> Response: # noqa: ANN401 """ Call connection.raw_get. @@ -430,7 +442,7 @@ class KeycloakOpenIDConnection(ConnectionManager): return r - def raw_post(self, *args: list, **kwargs: dict) -> Response: + def raw_post(self, *args: Any, **kwargs: Any) -> Response: # noqa: ANN401 """ Call connection.raw_post. @@ -452,7 +464,7 @@ class KeycloakOpenIDConnection(ConnectionManager): return r - def raw_put(self, *args: list, **kwargs: dict) -> Response: + def raw_put(self, *args: Any, **kwargs: Any) -> Response: # noqa: ANN401 """ Call connection.raw_put. @@ -474,7 +486,7 @@ class KeycloakOpenIDConnection(ConnectionManager): return r - def raw_delete(self, *args: list, **kwargs: dict) -> Response: + def raw_delete(self, *args: Any, **kwargs: Any) -> Response: # noqa: ANN401 """ Call connection.raw_delete. @@ -531,7 +543,7 @@ class KeycloakOpenIDConnection(ConnectionManager): b"Session not active", ] if e.response_code == HTTP_BAD_REQUEST and any( - err in e.response_body for err in list_errors + err in (e.response_body or b"") for err in list_errors ): await self.a_get_token() else: @@ -539,10 +551,10 @@ class KeycloakOpenIDConnection(ConnectionManager): async def a__refresh_if_required(self) -> None: """Refresh the token if it is expired.""" - if datetime.now(tz=timezone.utc) >= self.expires_at: + if self.expires_at is not None and datetime.now(tz=timezone.utc) >= self.expires_at: await self.a_refresh_token() - async def a_raw_get(self, *args: list, **kwargs: dict) -> AsyncResponse: + async def a_raw_get(self, *args: Any, **kwargs: Any) -> AsyncResponse: # noqa: ANN401 """ Call connection.raw_get. @@ -564,7 +576,7 @@ class KeycloakOpenIDConnection(ConnectionManager): return r - async def a_raw_post(self, *args: list, **kwargs: dict) -> AsyncResponse: + async def a_raw_post(self, *args: Any, **kwargs: Any) -> AsyncResponse: # noqa: ANN401 """ Call connection.raw_post. @@ -586,7 +598,7 @@ class KeycloakOpenIDConnection(ConnectionManager): return r - async def a_raw_put(self, *args: list, **kwargs: dict) -> AsyncResponse: + async def a_raw_put(self, *args: Any, **kwargs: Any) -> AsyncResponse: # noqa: ANN401 """ Call connection.raw_put. @@ -608,7 +620,7 @@ class KeycloakOpenIDConnection(ConnectionManager): return r - async def a_raw_delete(self, *args: list, **kwargs: dict) -> AsyncResponse: + async def a_raw_delete(self, *args: Any, **kwargs: Any) -> AsyncResponse: # noqa: ANN401 """ Call connection.raw_delete. diff --git a/src/keycloak/uma_permissions.py b/src/keycloak/uma_permissions.py index dc5cda6..54ab619 100644 --- a/src/keycloak/uma_permissions.py +++ b/src/keycloak/uma_permissions.py @@ -24,7 +24,7 @@ from __future__ import annotations -from keycloak.exceptions import KeycloakPermissionFormatError, PermissionDefinitionError +from keycloak.exceptions import KeycloakPermissionFormatError class UMAPermission: @@ -68,11 +68,9 @@ class UMAPermission: """ self.resource = resource self.scope = scope + self.resource_id = None - if permission: - if not isinstance(permission, UMAPermission): - msg = f"can't determine if '{permission}' is a resource or scope" - raise PermissionDefinitionError(msg) + if permission is not None: if permission.resource: self.resource = str(permission.resource) if permission.scope: @@ -146,10 +144,7 @@ class UMAPermission: if scope: result_scope = str(scope) - if permission: - if not isinstance(permission, UMAPermission): - msg = f"can't determine if '{permission}' is a resource or scope" - raise PermissionDefinitionError(msg) + if permission is not None: if permission.resource: result_resource = str(permission.resource) if permission.scope: @@ -168,7 +163,7 @@ class Resource(UMAPermission): :type resource: str """ - def __init__(self, resource: Resource) -> None: + def __init__(self, resource: str) -> None: """ Init method. @@ -188,7 +183,7 @@ class Scope(UMAPermission): :type scope: str """ - def __init__(self, scope: Scope) -> None: + def __init__(self, scope: str) -> None: """ Init method. @@ -213,7 +208,9 @@ class AuthStatus: :type missing_permissions: set """ - def __init__(self, is_logged_in: bool, is_authorized: bool, missing_permissions: set) -> None: + def __init__( + self, is_logged_in: bool, is_authorized: bool, missing_permissions: set | str + ) -> None: """ Init method. @@ -252,7 +249,9 @@ class AuthStatus: ) -def build_permission_param(permissions: str | list | dict) -> set: +def build_permission_param( + permissions: str | list | dict | UMAPermission | None | tuple | set, +) -> set: """ Transform permissions to a set, so they are usable for requests. @@ -268,6 +267,8 @@ def build_permission_param(permissions: str | list | dict) -> set: return {permissions} if isinstance(permissions, UMAPermission): return {str(permissions)} + if isinstance(permissions, (list, tuple, set)): + return set(permissions) try: # treat as dictionary of permissions result = set() diff --git a/test_keycloak_init.sh b/test_keycloak_init.sh index 74e15fd..457197c 100755 --- a/test_keycloak_init.sh +++ b/test_keycloak_init.sh @@ -6,9 +6,9 @@ KEYCLOAK_DOCKER_IMAGE="quay.io/keycloak/keycloak:$KEYCLOAK_DOCKER_IMAGE_TAG" function keycloak_stop() { if [ "$(docker ps -q -f name=unittest_keycloak)" ]; then - docker logs unittest_keycloak > keycloak_test_logs.txt - docker stop unittest_keycloak &> /dev/null - docker rm unittest_keycloak &> /dev/null + docker logs unittest_keycloak >keycloak_test_logs.txt + docker stop unittest_keycloak &>/dev/null + docker rm unittest_keycloak &>/dev/null fi } @@ -23,11 +23,11 @@ function keycloak_start() { docker run --rm -d --name unittest_keycloak -e KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN}" -e KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD}" -p "${KEYCLOAK_PORT}:8080" -v $PWD/tests/providers:/opt/keycloak/providers "${KEYCLOAK_DOCKER_IMAGE}" start-dev --features="${KEYCLOAK_FEATURES}" SECONDS=0 until curl --silent --output /dev/null localhost:$KEYCLOAK_PORT; do - sleep 5; - if [ ${SECONDS} -gt 180 ]; then - echo "Timeout exceeded"; - exit 1; - fi + sleep 5 + if [ ${SECONDS} -gt 180 ]; then + echo "Timeout exceeded" + exit 1 + fi done } diff --git a/tests/conftest.py b/tests/conftest.py index b5434c4..6b07af7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -481,6 +481,7 @@ def group(admin: KeycloakAdmin, realm: str) -> Generator[str, None, None]: admin.change_current_realm(realm) group_name = str(uuid.uuid4()) group_id = admin.create_group(payload={"name": group_name}) + assert group_id is not None yield group_id admin.delete_group(group_id=group_id) @@ -556,7 +557,7 @@ def composite_client_role( @pytest.fixture -def selfsigned_cert() -> tuple[str, str]: +def selfsigned_cert() -> tuple[bytes, bytes]: """ Generate self signed certificate for a hostname, and optional IP addresses. @@ -564,7 +565,7 @@ def selfsigned_cert() -> tuple[str, str]: :rtype: Tuple[str, str] """ hostname = "testcert" - ip_addresses = None + ip_addresses: None | list = [] key = None # Generate our key if key is None: @@ -575,7 +576,7 @@ def selfsigned_cert() -> tuple[str, str]: ) name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, hostname)]) - alt_names = [x509.DNSName(hostname)] + alt_names: list = [x509.DNSName(hostname)] # allow addressing by IP, for when you don't have real DNS (common in most testing scenarios if ip_addresses: diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index a0ccf8d..7c6ac67 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -2,6 +2,7 @@ import contextlib import copy +import json import os import uuid from inspect import iscoroutinefunction, signature @@ -117,7 +118,9 @@ def test_keycloak_admin_init(env: KeycloakTestEnv) -> None: "publicClient": False, }, ) - secret = admin.generate_client_secrets(client_id=admin.get_client_id("authz-client")) + client_id = admin.get_client_id("authz-client") + assert client_id is not None + secret = admin.generate_client_secrets(client_id=client_id) admin_auth = KeycloakAdmin( server_url=f"http://{env.keycloak_host}:{env.keycloak_port}", user_realm_name="authz", @@ -172,6 +175,7 @@ def test_realms(admin: KeycloakAdmin) -> None: with pytest.raises(KeycloakPostError) as err: res = admin.create_realm(payload={"realm": "test"}) + assert isinstance(err.value.error_message, bytes) assert ( b"Realm test already exists" in err.value.error_message or b"Conflict detected" in err.value.error_message @@ -180,9 +184,9 @@ def test_realms(admin: KeycloakAdmin) -> None: # Create the same realm, skip_exists true res = admin.create_realm(payload={"realm": "test"}, skip_exists=True) assert res in [ - {"errorMessage": "Realm test already exists"}, - {"msg": "Already exists"}, - {"errorMessage": "Conflict detected. See logs for details"}, + json.dumps({"errorMessage": "Realm test already exists"}).encode(), + json.dumps({"msg": "Already exists"}).encode(), + json.dumps({"errorMessage": "Conflict detected. See logs for details"}).encode(), ], res # Get a single realm @@ -280,7 +284,7 @@ def test_import_export_realms(admin: KeycloakAdmin, realm: str) -> None: assert realm_export != {}, realm_export admin.delete_realm(realm_name=realm) - admin.realm_name = "master" + admin.change_current_realm("master") res = admin.import_realm(payload=realm_export) assert res == b"", res @@ -368,12 +372,19 @@ def test_organizations(admin: KeycloakAdmin, realm: str) -> None: assert org["alias"] == "test-org01", org["alias"] assert org["domains"][0]["name"] == "org1.com", org["domains"][0]["name"] + org["name"] = "test-org01-u" + org_update_res = admin.update_organization(org_id, payload=org) + assert org_update_res == {} + org["name"] = "test-org01" + admin.update_organization(org_id, payload=org) + orgs = admin.get_organizations() assert len(orgs) == 1, orgs assert orgs[0]["name"] == "test-org01", orgs[0]["name"] user_id = admin.create_user(payload={"username": "test", "email": "test@test.test"}) - admin.organization_user_add(user_id, org_id) + org_user_add_res = admin.organization_user_add(user_id, org_id) + assert org_user_add_res == b"" users = admin.get_organization_members(org_id) assert len(users) == 1, users @@ -385,7 +396,8 @@ def test_organizations(admin: KeycloakAdmin, realm: str) -> None: assert len(user_orgs) == 1, user_orgs assert user_orgs[0]["name"] == "test-org01", user_orgs[0]["name"] - admin.organization_user_remove(user_id, org_id) + org_user_remove_res = admin.organization_user_remove(user_id, org_id) + assert org_user_remove_res == {} users = admin.get_organization_members(org_id) assert len(users) == 0, users num_users = admin.get_organization_members_count(org_id) @@ -415,17 +427,20 @@ def test_organizations(admin: KeycloakAdmin, realm: str) -> None: } ) - admin.organization_idp_add(org_id, "github") + idp_add_res = admin.organization_idp_add(org_id, "github") + assert idp_add_res == {} idps = admin.get_organization_idps(org_id) assert len(idps) == 1, idps assert idps[0]["alias"] == "github", idps[0]["alias"] - admin.organization_idp_remove(org_id, "github") + idp_remove_res = admin.organization_idp_remove(org_id, "github") + assert idp_remove_res == {} idps = admin.get_organization_idps(org_id) assert len(idps) == 0, idps - admin.delete_organization(org_id) + delete_org_res = admin.delete_organization(org_id) + assert delete_org_res == {} orgs = admin.get_organizations() assert len(orgs) == 0, orgs @@ -673,6 +688,7 @@ def test_user_groups_pagination(admin: KeycloakAdmin, realm: str) -> None: for ind in range(admin.PAGE_SIZE + 50): group_name = f"group_{ind}" group_id = admin.create_group(payload={"name": group_name}) + assert group_id is not None admin.group_user_add(user_id=user_id, group_id=group_id) groups = admin.get_user_groups(user_id=user_id) @@ -954,6 +970,7 @@ def test_groups(admin: KeycloakAdmin, user: str) -> None: assert {x["id"] for x in groups[0]["subGroups"]} == {subgroup_id_1, subgroup_id_2} # Test get group + assert subgroup_id_1 is not None res = admin.get_group(group_id=subgroup_id_1) assert res["id"] == subgroup_id_1, res assert res["name"] == "subgroup-1" @@ -969,6 +986,7 @@ def test_groups(admin: KeycloakAdmin, user: str) -> None: main_group = admin.get_group(group_id=group_id) # Test nested searches + assert subgroup_id_2 is not None subgroup_2 = admin.get_group(group_id=subgroup_id_2) res = admin.get_subgroups(group=subgroup_2, path="/main-group/subgroup-2/subsubgroup-1") assert res is not None, res @@ -979,6 +997,7 @@ def test_groups(admin: KeycloakAdmin, user: str) -> None: group=admin.get_group(group_id=group_id, full_hierarchy=True), path="/main-group/subgroup-2/subsubgroup-1", ) + assert isinstance(res, dict) assert res["id"] == subsubgroup_id_1 # Test nested search from all groups @@ -1060,7 +1079,7 @@ def test_groups(admin: KeycloakAdmin, user: str) -> None: res = admin.group_set_permissions(group_id=subgroup_id_2, enabled=False) assert not res["enabled"], res with pytest.raises(KeycloakPutError) as err: - admin.group_set_permissions(group_id=subgroup_id_2, enabled="blah") + admin.group_set_permissions(group_id=subgroup_id_2, enabled="blah") # pyright: ignore[reportArgumentType] assert err.match(UNKOWN_ERROR_REGEX), err # Test update group @@ -1076,6 +1095,7 @@ def test_groups(admin: KeycloakAdmin, user: str) -> None: # Test delete res = admin.delete_group(group_id=group_id) assert res == {}, res + assert main_group_id_2 is not None res = admin.delete_group(group_id=main_group_id_2) assert res == {}, res assert len(admin.get_groups()) == 0 @@ -1248,11 +1268,12 @@ def test_clients(admin: KeycloakAdmin, realm: str) -> None: assert res["name"] == "temp-resource", res temp_resource_id: str = res["_id"] # Test update authz resources - admin.update_client_authz_resource( + cz_res = admin.update_client_authz_resource( client_id=auth_client_id, resource_id=temp_resource_id, payload={"name": "temp-updated-resource"}, ) + assert cz_res == {} res = admin.get_client_authz_resource(client_id=auth_client_id, resource_id=temp_resource_id) assert res["name"] == "temp-updated-resource", res with pytest.raises(KeycloakPutError) as err: @@ -1262,7 +1283,10 @@ def test_clients(admin: KeycloakAdmin, realm: str) -> None: payload={"name": "temp-updated-resource"}, ) assert err.match("404: b''"), err - admin.delete_client_authz_resource(client_id=auth_client_id, resource_id=temp_resource_id) + cz_res = admin.delete_client_authz_resource( + client_id=auth_client_id, resource_id=temp_resource_id + ) + assert cz_res == {} with pytest.raises(KeycloakGetError) as err: admin.get_client_authz_resource(client_id=auth_client_id, resource_id=temp_resource_id) assert err.match("404: b''") @@ -1304,7 +1328,8 @@ def test_clients(admin: KeycloakAdmin, realm: str) -> None: ) res2 = admin.get_client_authz_policy(client_id=auth_client_id, policy_id=res["id"]) assert res["id"] == res2["id"] - admin.delete_client_authz_policy(client_id=auth_client_id, policy_id=res["id"]) + cz_res = admin.delete_client_authz_policy(client_id=auth_client_id, policy_id=res["id"]) + assert cz_res == {} with pytest.raises(KeycloakGetError) as err: admin.get_client_authz_policy(client_id=auth_client_id, policy_id=res["id"]) assert err.match("404: b''") @@ -1387,6 +1412,7 @@ def test_clients(admin: KeycloakAdmin, realm: str) -> None: "policies": [role_based_policy_id], }, ) + assert res == b"" # Test getting associated policies for a permission associated_policies = admin.get_client_authz_permission_associated_policies( @@ -1447,7 +1473,8 @@ def test_clients(admin: KeycloakAdmin, realm: str) -> None: "config": {"hourEnd": "18", "hour": "9"}, } ] - admin.import_client_authz_config(client_id=auth_client_id, payload=authz_config) + cz_res = admin.import_client_authz_config(client_id=auth_client_id, payload=authz_config) + assert cz_res == {} exported = admin.get_client_authz_settings(client_id=auth_client_id) assert ( len( @@ -1496,23 +1523,22 @@ def test_clients(admin: KeycloakAdmin, realm: str) -> None: admin.get_client_secrets(client_id="does-not-exist") assert err.match('404: b\'{"error":"Could not find client".*}\'') - secrets = admin.get_client_secrets( - client_id=admin.get_client_id(client_id="test-confidential"), - ) + client_id = admin.get_client_id(client_id="test-confidential") + assert client_id is not None + secrets = admin.get_client_secrets(client_id=client_id) assert secrets == {"type": "secret", "value": "test-secret"} with pytest.raises(KeycloakPostError) as err: admin.generate_client_secrets(client_id="does-not-exist") assert err.match('404: b\'{"error":"Could not find client".*}\'') - res = admin.generate_client_secrets( - client_id=admin.get_client_id(client_id="test-confidential"), - ) + client_id = admin.get_client_id(client_id="test-confidential") + assert client_id is not None + res = admin.generate_client_secrets(client_id=client_id) assert res - assert ( - admin.get_client_secrets(client_id=admin.get_client_id(client_id="test-confidential")) - == res - ) + client_id = admin.get_client_id(client_id="test-confidential") + assert client_id is not None + assert admin.get_client_secrets(client_id=client_id) == res def test_realm_roles(admin: KeycloakAdmin, realm: str) -> None: @@ -1619,6 +1645,7 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str) -> None: # Test realm role group assignment group_id = admin.create_group(payload={"name": "test-group"}) + assert group_id is not None with pytest.raises(KeycloakPostError) as err: admin.assign_group_realm_roles(group_id=group_id, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err @@ -2075,16 +2102,18 @@ def test_client_default_client_scopes(admin: KeycloakAdmin, realm: str, client: "client": client_id, "clientScopeId": new_client_scope_id, } - admin.add_client_default_client_scope( + dcs_res = admin.add_client_default_client_scope( client_id, new_client_scope_id, new_default_client_scope_data, ) + assert dcs_res == {} default_client_scopes = admin.get_client_default_client_scopes(client_id) assert len(default_client_scopes) in [6, 7], default_client_scopes # Test remove a client default scope - admin.delete_client_default_client_scope(client_id, new_client_scope_id) + dcs_res = admin.delete_client_default_client_scope(client_id, new_client_scope_id) + assert dcs_res == {} default_client_scopes = admin.get_client_default_client_scopes(client_id) assert len(default_client_scopes) in [5, 6], default_client_scopes @@ -2125,16 +2154,18 @@ def test_client_optional_client_scopes(admin: KeycloakAdmin, realm: str, client: "client": client_id, "clientScopeId": new_client_scope_id, } - admin.add_client_optional_client_scope( + ocs_res = admin.add_client_optional_client_scope( client_id, new_client_scope_id, new_optional_client_scope_data, ) + assert ocs_res == {} optional_client_scopes = admin.get_client_optional_client_scopes(client_id) assert len(optional_client_scopes) in [5, 6], optional_client_scopes # Test remove a client optional scope - admin.delete_client_optional_client_scope(client_id, new_client_scope_id) + ocs_res = admin.delete_client_optional_client_scope(client_id, new_client_scope_id) + assert ocs_res == {} optional_client_scopes = admin.get_client_optional_client_scopes(client_id) assert len(optional_client_scopes) in [4, 5], optional_client_scopes @@ -2243,11 +2274,12 @@ def test_client_roles(admin: KeycloakAdmin, client: str) -> None: with pytest.raises(KeycloakDeleteError) as err: admin.delete_client_roles_of_user(user_id=user_id, client_id=client, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err - admin.delete_client_roles_of_user( + dcr_res = admin.delete_client_roles_of_user( user_id=user_id, client_id=client, roles=[admin.get_client_role(client_id=client, role_name="client-role-test-update")], ) + assert dcr_res == {} assert len(admin.get_client_roles_of_user(user_id=user_id, client_id=client)) == 0 # Test groups and client roles @@ -2258,6 +2290,7 @@ def test_client_roles(admin: KeycloakAdmin, client: str) -> None: assert err.match(COULD_NOT_FIND_ROLE_REGEX) group_id = admin.create_group(payload={"name": "test-group"}) + assert group_id is not None res = admin.get_group_client_roles(group_id=group_id, client_id=client) assert len(res) == 0 with pytest.raises(KeycloakGetError) as err: @@ -2409,10 +2442,11 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str) -> None: pytest.fail("Missing realm management client") # Enable permissions on the Superset client - admin.update_client_management_permissions( + update_res = admin.update_client_management_permissions( payload={"enabled": True}, client_id=target_client_id, ) + assert isinstance(update_res, dict) # Fetch various IDs and strings needed when creating the permission token_exchange_permission_id = admin.get_client_management_permissions( @@ -2466,7 +2500,7 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str) -> None: client_id=realm_management_id, scope_id=token_exchange_permission_id, )["name"] - admin.update_client_authz_scope_permission( + per_update_res = admin.update_client_authz_scope_permission( payload={ "id": token_exchange_permission_id, "name": permission_name, @@ -2480,9 +2514,10 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str) -> None: client_id=realm_management_id, scope_id=token_exchange_permission_id, ) + assert per_update_res == b"" # Create permissions on the target client to reference this policy - admin.create_client_authz_scope_permission( + per_create_res = admin.create_client_authz_scope_permission( payload={ "id": "some-id", "name": "test-permission", @@ -2495,6 +2530,7 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str) -> None: }, client_id=realm_management_id, ) + assert isinstance(per_create_res, dict) permission_name = admin.get_client_authz_scope_permission( client_id=realm_management_id, scope_id=token_exchange_permission_id, @@ -2517,10 +2553,11 @@ def test_email(admin: KeycloakAdmin, user: str) -> None: :param user: Keycloak user :type user: str """ + admin.enable_user(user) # Emails will fail as we don't have SMTP test setup with pytest.raises(KeycloakPutError) as err: - admin.send_update_account(user_id=user, payload={}) - assert err.match(UNKOWN_ERROR_REGEX), err + admin.send_update_account(user_id=user, payload=[]) + assert err.match('500: b\'{"errorMessage":"Failed to send execute actions email: .*'), err admin.update_user(user_id=user, payload={"enabled": True}) with pytest.raises(KeycloakPutError) as err: @@ -2535,7 +2572,10 @@ def test_get_sessions(admin: KeycloakAdmin) -> None: :param admin: Keycloak Admin client :type admin: KeycloakAdmin """ - sessions = admin.get_sessions(user_id=admin.get_user_id(username=admin.connection.username)) + assert admin.connection.username is not None + user_id = admin.get_user_id(username=admin.connection.username) + assert user_id is not None + sessions = admin.get_sessions(user_id=user_id) assert len(sessions) >= 1 with pytest.raises(KeycloakGetError) as err: admin.get_sessions(user_id="bad") @@ -2654,6 +2694,7 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str) -> None: del flow["id"] flow["description"] = "test description" res = admin.update_authentication_flow(flow_id=browser_flow_id, payload=flow) + assert res == {} res = admin.get_authentication_flow_for_id(flow_id=browser_flow_id) assert res["description"] == "test description" @@ -2707,7 +2748,7 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str) -> None: payload = admin.get_authentication_flow_executions(flow_alias="test-create")[0] payload["displayName"] = "test" res = admin.update_authentication_flow_executions(payload=payload, flow_alias="test-create") - assert res or (res == {}) + assert res == {} exec_id = admin.get_authentication_flow_executions(flow_alias="test-create")[0]["id"] res = admin.delete_authentication_flow_execution(execution_id=exec_id) @@ -2743,7 +2784,7 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str) -> None: flow_alias="test-create", skip_exists=True, ) - assert res == {"msg": "Already exists"} + assert res == json.dumps({"msg": "Already exists"}).encode() # Test delete auth flow flow_id = next(x for x in admin.get_authentication_flows() if x["alias"] == "test-browser")[ @@ -2814,13 +2855,14 @@ def test_authentication_configs(admin: KeycloakAdmin, realm: str) -> None: # Test authenticator config executions = admin.get_authentication_flow_executions(flow_alias="browser") execution = next(ex for ex in executions if ex["configurable"]) - _ = admin.create_execution_config( + res = admin.create_execution_config( execution["id"], { "alias": "test.provisioning.property", "config": {"test.provisioning.property": "value2"}, }, ) + assert res == b"" executions = admin.get_authentication_flow_executions(flow_alias="browser") execution_config_id = next(ex for ex in executions if ex.get("id") == execution["id"])[ "authenticationConfig" @@ -2836,10 +2878,16 @@ def test_authentication_configs(admin: KeycloakAdmin, realm: str) -> None: admin.update_authenticator_config(payload={}, config_id="bad") assert err.match('404: b\'{"error":"Could not find authenticator config".*}\'') + res = admin.update_authenticator_config(payload={}, config_id=execution_config_id) + assert res == {} + with pytest.raises(KeycloakDeleteError) as err: admin.delete_authenticator_config(config_id="bad") assert err.match('404: b\'{"error":"Could not find authenticator config".*}\'') + res = admin.delete_authenticator_config(config_id=execution_config_id) + assert res == {} + def test_sync_users(admin: KeycloakAdmin, realm: str) -> None: """ @@ -3167,12 +3215,14 @@ def test_auto_refresh(admin_frozen: KeycloakAdmin, realm: str) -> None: assert res["realm"] == realm # Freeze time to simulate the access token expiring + assert admin.connection.expires_at is not None with freezegun.freeze_time("2023-02-25 10:05:00"): assert admin.connection.expires_at < datetime_parser.parse("2023-02-25T10:05:00Z") assert admin.get_realm(realm_name=realm) assert admin.connection.expires_at > datetime_parser.parse("2023-02-25T10:05:00Z") # Test bad refresh token, but first make sure access token has expired again + assert admin.connection.token is not None with freezegun.freeze_time("2023-02-25 10:10:00"): admin.connection.custom_headers = {"Content-Type": "application/json"} admin.connection.token["refresh_token"] = "bad" # noqa: S105 @@ -3244,6 +3294,7 @@ def test_get_required_action_by_alias(admin: KeycloakAdmin, realm: str) -> None: admin.change_current_realm(realm) ractions = admin.get_required_actions() ra = admin.get_required_action_by_alias("UPDATE_PASSWORD") + assert ra is not None assert ra in ractions assert ra["alias"] == "UPDATE_PASSWORD" assert admin.get_required_action_by_alias("does-not-exist") is None @@ -3260,10 +3311,13 @@ def test_update_required_action(admin: KeycloakAdmin, realm: str) -> None: """ admin.change_current_realm(realm) ra = admin.get_required_action_by_alias("UPDATE_PASSWORD") + assert ra is not None old = copy.deepcopy(ra) ra["enabled"] = False - admin.update_required_action("UPDATE_PASSWORD", ra) + res = admin.update_required_action("UPDATE_PASSWORD", ra) + assert res == {} newra = admin.get_required_action_by_alias("UPDATE_PASSWORD") + assert newra is not None assert old != newra assert newra["enabled"] is False @@ -3370,7 +3424,7 @@ def test_upload_certificate( admin: KeycloakAdmin, realm: str, client: str, - selfsigned_cert: tuple, + selfsigned_cert: tuple[bytes, bytes], ) -> None: """ Test upload certificate. @@ -3387,7 +3441,8 @@ def test_upload_certificate( admin.change_current_realm(realm) cert, _ = selfsigned_cert cert = cert.decode("utf-8").strip() - admin.upload_certificate(client, cert) + res = admin.upload_certificate(client, cert) + assert "certificate" in res cl = admin.get_client(client) assert cl["attributes"]["jwt.credential.certificate"] == "".join(cert.splitlines()[1:-1]) @@ -3420,6 +3475,7 @@ def test_get_bruteforce_status_for_user( oid.token(username=username, password="wrongpassword") # noqa: S106 user_id = admin.get_user_id(username) + assert user_id is not None bruteforce_status = admin.get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 1 @@ -3458,10 +3514,12 @@ def test_clear_bruteforce_attempts_for_user( oid.token(username=username, password="wrongpassword") # noqa: S106 user_id = admin.get_user_id(username) + assert user_id is not None bruteforce_status = admin.get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 1 res = admin.clear_bruteforce_attempts_for_user(user_id) + assert res == {} bruteforce_status = admin.get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 0 @@ -3499,10 +3557,12 @@ def test_clear_bruteforce_attempts_for_all_users( oid.token(username=username, password="wrongpassword") # noqa: S106 user_id = admin.get_user_id(username) + assert user_id is not None bruteforce_status = admin.get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 1 res = admin.clear_all_bruteforce_attempts() + assert res == {} bruteforce_status = admin.get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 0 @@ -3680,7 +3740,10 @@ def test_refresh_token(admin: KeycloakAdmin) -> None: """ admin.get_realms() assert admin.connection.token is not None - admin.user_logout(admin.get_user_id(admin.connection.username)) + assert admin.connection.username is not None + user_id = admin.get_user_id(admin.connection.username) + assert user_id is not None + admin.user_logout(user_id=user_id) admin.connection.refresh_token() @@ -3759,6 +3822,7 @@ async def test_a_realms(admin: KeycloakAdmin) -> None: with pytest.raises(KeycloakPostError) as err: res = await admin.a_create_realm(payload={"realm": "test"}) + assert isinstance(err.value.error_message, bytes) assert ( b"Realm test already exists" in err.value.error_message or b"Conflict detected" in err.value.error_message @@ -3767,9 +3831,9 @@ async def test_a_realms(admin: KeycloakAdmin) -> None: # Create the same realm, skip_exists true res = await admin.a_create_realm(payload={"realm": "test"}, skip_exists=True) assert res in [ - {"errorMessage": "Realm test already exists"}, - {"msg": "Already exists"}, - {"errorMessage": "Conflict detected. See logs for details"}, + json.dumps({"errorMessage": "Realm test already exists"}).encode(), + json.dumps({"msg": "Already exists"}).encode(), + json.dumps({"errorMessage": "Conflict detected. See logs for details"}).encode(), ], res # Get a single realm @@ -3870,7 +3934,7 @@ async def test_a_import_export_realms(admin: KeycloakAdmin, realm: str) -> None: assert realm_export != {}, realm_export await admin.a_delete_realm(realm_name=realm) - admin.realm_name = "master" + admin.change_current_realm("master") res = await admin.a_import_realm(payload=realm_export) assert res == b"", res @@ -3960,12 +4024,19 @@ async def a_test_organizations(admin: KeycloakAdmin, realm: str) -> None: assert org["alias"] == "test-org01", org["alias"] assert org["domains"][0]["name"] == "org1.com", org["domains"][0]["name"] + org["name"] = "test-org01-u" + org_update_res = await admin.a_update_organization(org_id, org) + assert org_update_res == {} + org["name"] = "test-org01" + await admin.a_update_organization(org_id, org) + orgs = await admin.a_get_organizations() assert len(orgs) == 1, orgs assert orgs[0]["name"] == "test-org01", orgs[0]["name"] user_id = await admin.a_create_user(payload={"username": "test", "email": "test@test.test"}) - await admin.a_organization_user_add(user_id, org_id) + org_user_add_res = await admin.a_organization_user_add(user_id, org_id) + assert org_user_add_res == b"" users = await admin.a_get_organization_members(org_id) assert len(users) == 1, users @@ -3977,7 +4048,8 @@ async def a_test_organizations(admin: KeycloakAdmin, realm: str) -> None: assert len(user_orgs) == 1, user_orgs assert user_orgs[0]["name"] == "test-org01", user_orgs[0]["name"] - await admin.a_organization_user_remove(user_id, org_id) + org_user_remove_res = await admin.a_organization_user_remove(user_id, org_id) + assert org_user_remove_res == {} users = await admin.a_get_organization_members(org_id) assert len(users) == 0, users num_users = await admin.a_get_organization_members_count(org_id) @@ -4011,17 +4083,20 @@ async def a_test_organizations(admin: KeycloakAdmin, realm: str) -> None: } ) - await admin.a_organization_idp_add(org_id, "github") + idp_add_res = await admin.a_organization_idp_add(org_id, "github") + assert idp_add_res == {} idps = await admin.a_get_organization_idps(org_id) assert len(idps) == 1, idps assert idps[0]["alias"] == "github", idps[0]["alias"] - await admin.a_organization_idp_remove(org_id, "github") + idp_remove_res = await admin.a_organization_idp_remove(org_id, "github") + assert idp_remove_res == {} idps = await admin.a_get_organization_idps(org_id) assert len(idps) == 0, idps - await admin.a_delete_organization(org_id) + org_delete_res = await admin.a_delete_organization(org_id) + assert org_delete_res == {} orgs = await admin.a_get_organizations() assert len(orgs) == 0, orgs @@ -4276,6 +4351,7 @@ async def test_a_user_groups_pagination(admin: KeycloakAdmin, realm: str) -> Non for ind in range(admin.PAGE_SIZE + 50): group_name = f"group_{ind}" group_id = await admin.a_create_group(payload={"name": group_name}) + assert group_id is not None await admin.a_group_user_add(user_id=user_id, group_id=group_id) groups = await admin.a_get_user_groups(user_id=user_id) @@ -4568,6 +4644,7 @@ async def test_a_groups(admin: KeycloakAdmin, user: str) -> None: assert {x["id"] for x in groups[0]["subGroups"]} == {subgroup_id_1, subgroup_id_2} # Test get group + assert subgroup_id_1 is not None res = await admin.a_get_group(group_id=subgroup_id_1) assert res["id"] == subgroup_id_1, res assert res["name"] == "subgroup-1" @@ -4586,6 +4663,7 @@ async def test_a_groups(admin: KeycloakAdmin, user: str) -> None: main_group = await admin.a_get_group(group_id=group_id) # Test nested searches + assert subgroup_id_2 is not None subgroup_2 = await admin.a_get_group(group_id=subgroup_id_2) res = await admin.a_get_subgroups( group=subgroup_2, @@ -4599,6 +4677,7 @@ async def test_a_groups(admin: KeycloakAdmin, user: str) -> None: group=await admin.a_get_group(group_id=group_id, full_hierarchy=True), path="/main-group/subgroup-2/subsubgroup-1", ) + assert res is not None assert res["id"] == subsubgroup_id_1 # Test nested search from all groups @@ -4633,8 +4712,9 @@ async def test_a_groups(admin: KeycloakAdmin, user: str) -> None: assert res is not None, res assert res["id"] == subgroup_id_1, res - res = await admin.a_get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1/test") - assert res["error"] == "Group path does not exist" + with pytest.raises(KeycloakGetError) as err: + await admin.a_get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1/test") + assert err.match('404: b\'{"error":"Group path does not exist"}\'') res = await admin.a_get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1") assert res is not None, res @@ -4678,7 +4758,7 @@ async def test_a_groups(admin: KeycloakAdmin, user: str) -> None: res = await admin.a_group_set_permissions(group_id=subgroup_id_2, enabled=False) assert not res["enabled"], res with pytest.raises(KeycloakPutError) as err: - await admin.a_group_set_permissions(group_id=subgroup_id_2, enabled="blah") + await admin.a_group_set_permissions(group_id=subgroup_id_2, enabled="blah") # pyright: ignore[reportArgumentType] assert err.match(UNKOWN_ERROR_REGEX), err # Test update group @@ -4694,6 +4774,7 @@ async def test_a_groups(admin: KeycloakAdmin, user: str) -> None: # Test delete res = await admin.a_delete_group(group_id=group_id) assert res == {}, res + assert main_group_id_2 is not None res = await admin.a_delete_group(group_id=main_group_id_2) assert res == {}, res assert len(await admin.a_get_groups()) == 0 @@ -4883,11 +4964,12 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str) -> None: assert res["name"] == "temp-resource", res temp_resource_id: str = res["_id"] # Test update authz resources - await admin.a_update_client_authz_resource( + res = await admin.a_update_client_authz_resource( client_id=auth_client_id, resource_id=temp_resource_id, payload={"name": "temp-updated-resource"}, ) + assert res == {} res = await admin.a_get_client_authz_resource( client_id=auth_client_id, resource_id=temp_resource_id, @@ -4900,10 +4982,11 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str) -> None: payload={"name": "temp-updated-resource"}, ) assert err.match("404: b''"), err - await admin.a_delete_client_authz_resource( + res = await admin.a_delete_client_authz_resource( client_id=auth_client_id, resource_id=temp_resource_id, ) + assert res == {} with pytest.raises(KeycloakGetError) as err: await admin.a_get_client_authz_resource( client_id=auth_client_id, @@ -4948,7 +5031,8 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str) -> None: ) res2 = await admin.a_get_client_authz_policy(client_id=auth_client_id, policy_id=res["id"]) assert res["id"] == res2["id"] - await admin.a_delete_client_authz_policy(client_id=auth_client_id, policy_id=res["id"]) + res3 = await admin.a_delete_client_authz_policy(client_id=auth_client_id, policy_id=res["id"]) + assert res3 == {} with pytest.raises(KeycloakGetError) as err: await admin.a_get_client_authz_policy(client_id=auth_client_id, policy_id=res["id"]) assert err.match("404: b''") @@ -5031,6 +5115,7 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str) -> None: "policies": [role_based_policy_id], }, ) + assert res == b"" # Test getting associated policies for a permission associated_policies = await admin.a_get_client_authz_permission_associated_policies( @@ -5090,7 +5175,8 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str) -> None: "config": {"hourEnd": "18", "hour": "9"}, } ] - await admin.a_import_client_authz_config(client_id=auth_client_id, payload=authz_config) + res = await admin.a_import_client_authz_config(client_id=auth_client_id, payload=authz_config) + assert res == {} exported = await admin.a_get_client_authz_settings(client_id=auth_client_id) assert ( len( @@ -5139,25 +5225,24 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str) -> None: await admin.a_get_client_secrets(client_id="does-not-exist") assert err.match('404: b\'{"error":"Could not find client".*}\'') - secrets = await admin.a_get_client_secrets( - client_id=await admin.a_get_client_id(client_id="test-confidential"), - ) + client_id = await admin.a_get_client_id(client_id="test-confidential") + assert client_id is not None + + secrets = await admin.a_get_client_secrets(client_id=client_id) assert secrets == {"type": "secret", "value": "test-secret"} with pytest.raises(KeycloakPostError) as err: await admin.a_generate_client_secrets(client_id="does-not-exist") assert err.match('404: b\'{"error":"Could not find client".*}\'') - res = await admin.a_generate_client_secrets( - client_id=await admin.a_get_client_id(client_id="test-confidential"), - ) + client_id = await admin.a_get_client_id(client_id="test-confidential") + assert client_id is not None + + res = await admin.a_generate_client_secrets(client_id=client_id) assert res - assert ( - await admin.a_get_client_secrets( - client_id=await admin.a_get_client_id(client_id="test-confidential"), - ) - == res - ) + client_id = await admin.a_get_client_id(client_id="test-confidential") + assert client_id is not None + assert await admin.a_get_client_secrets(client_id=client_id) == res @pytest.mark.asyncio @@ -5274,6 +5359,7 @@ async def test_a_realm_roles(admin: KeycloakAdmin, realm: str) -> None: # Test realm role group assignment group_id = await admin.a_create_group(payload={"name": "test-group"}) + assert group_id is not None with pytest.raises(KeycloakPostError) as err: await admin.a_assign_group_realm_roles(group_id=group_id, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err @@ -5748,16 +5834,18 @@ async def test_a_client_default_client_scopes( "client": client_id, "clientScopeId": new_client_scope_id, } - await admin.a_add_client_default_client_scope( + res = await admin.a_add_client_default_client_scope( client_id, new_client_scope_id, new_default_client_scope_data, ) + assert res == {} default_client_scopes = await admin.a_get_client_default_client_scopes(client_id) assert len(default_client_scopes) in [6, 7], default_client_scopes # Test remove a client default scope - await admin.a_delete_client_default_client_scope(client_id, new_client_scope_id) + res = await admin.a_delete_client_default_client_scope(client_id, new_client_scope_id) + assert res == {} default_client_scopes = await admin.a_get_client_default_client_scopes(client_id) assert len(default_client_scopes) in [5, 6], default_client_scopes @@ -5803,16 +5891,18 @@ async def test_a_client_optional_client_scopes( "client": client_id, "clientScopeId": new_client_scope_id, } - await admin.a_add_client_optional_client_scope( + res = await admin.a_add_client_optional_client_scope( client_id, new_client_scope_id, new_optional_client_scope_data, ) + assert res == {} optional_client_scopes = await admin.a_get_client_optional_client_scopes(client_id) assert len(optional_client_scopes) in [5, 6], optional_client_scopes # Test remove a client optional scope - await admin.a_delete_client_optional_client_scope(client_id, new_client_scope_id) + res = await admin.a_delete_client_optional_client_scope(client_id, new_client_scope_id) + assert res == {} optional_client_scopes = await admin.a_get_client_optional_client_scopes(client_id) assert len(optional_client_scopes) in [4, 5], optional_client_scopes @@ -5935,13 +6025,14 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str) -> None: with pytest.raises(KeycloakDeleteError) as err: await admin.a_delete_client_roles_of_user(user_id=user_id, client_id=client, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err - await admin.a_delete_client_roles_of_user( + res = await admin.a_delete_client_roles_of_user( user_id=user_id, client_id=client, roles=[ await admin.a_get_client_role(client_id=client, role_name="client-role-test-update"), ], ) + assert res == {} assert len(await admin.a_get_client_roles_of_user(user_id=user_id, client_id=client)) == 0 # Test groups and client roles @@ -5955,6 +6046,7 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str) -> None: assert err.match(COULD_NOT_FIND_ROLE_REGEX) group_id = await admin.a_create_group(payload={"name": "test-group"}) + assert group_id is not None res = await admin.a_get_group_client_roles(group_id=group_id, client_id=client) assert len(res) == 0 with pytest.raises(KeycloakGetError) as err: @@ -6119,10 +6211,11 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str) -> None pytest.fail("Missing realm management client") # Enable permissions on the Superset client - await admin.a_update_client_management_permissions( + res = await admin.a_update_client_management_permissions( payload={"enabled": True}, client_id=target_client_id, ) + assert isinstance(res, dict) # Fetch various IDs and strings needed when creating the permission token_exchange_permission_id = ( @@ -6180,7 +6273,7 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str) -> None scope_id=token_exchange_permission_id, ) )["name"] - await admin.a_update_client_authz_scope_permission( + res = await admin.a_update_client_authz_scope_permission( payload={ "id": token_exchange_permission_id, "name": permission_name, @@ -6194,9 +6287,10 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str) -> None client_id=realm_management_id, scope_id=token_exchange_permission_id, ) + assert res == b"" # Create permissions on the target client to reference this policy - await admin.a_create_client_authz_scope_permission( + res = await admin.a_create_client_authz_scope_permission( payload={ "id": "some-id", "name": "test-permission", @@ -6209,6 +6303,7 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str) -> None }, client_id=realm_management_id, ) + assert isinstance(res, dict) permission_name = ( await admin.a_get_client_authz_scope_permission( client_id=realm_management_id, @@ -6234,10 +6329,11 @@ async def test_a_email(admin: KeycloakAdmin, user: str) -> None: :param user: Keycloak user :type user: str """ + admin.enable_user(user) # Emails will fail as we don't have SMTP test setup with pytest.raises(KeycloakPutError) as err: - await admin.a_send_update_account(user_id=user, payload={}) - assert err.match(UNKOWN_ERROR_REGEX), err + await admin.a_send_update_account(user_id=user, payload=[]) + assert err.match('500: b\'{"errorMessage":"Failed to send execute actions email: .*'), err admin.update_user(user_id=user, payload={"enabled": True}) with pytest.raises(KeycloakPutError) as err: @@ -6309,9 +6405,11 @@ async def test_a_get_sessions(admin: KeycloakAdmin) -> None: :param admin: Keycloak Admin client :type admin: KeycloakAdmin """ - sessions = await admin.a_get_sessions( - user_id=admin.get_user_id(username=admin.connection.username), - ) + assert admin.connection.username is not None + user_id = admin.get_user_id(username=admin.connection.username) + assert user_id is not None + + sessions = await admin.a_get_sessions(user_id=user_id) assert len(sessions) >= 1 with pytest.raises(KeycloakGetError) as err: await admin.a_get_sessions(user_id="bad") @@ -6424,10 +6522,13 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str) -> None: payload={"alias": "test-create", "builtIn": False}, ) assert err.match('409: b\'{"errorMessage":"Flow test-create already exists"}\'') - assert await admin.a_create_authentication_flow( - payload={"alias": "test-create"}, - skip_exists=True, - ) == {"msg": "Already exists"} + assert ( + await admin.a_create_authentication_flow( + payload={"alias": "test-create"}, + skip_exists=True, + ) + == json.dumps({"msg": "Already exists"}).encode() + ) # Update res = await admin.a_get_authentication_flows() @@ -6437,6 +6538,7 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str) -> None: del flow["id"] flow["description"] = "test description" res = await admin.a_update_authentication_flow(flow_id=browser_flow_id, payload=flow) + assert res == {} res = await admin.a_get_authentication_flow_for_id(flow_id=browser_flow_id) assert res["description"] == "test description" @@ -6493,7 +6595,7 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str) -> None: payload=payload, flow_alias="test-create", ) - assert res or (res == {}) + assert res == {} exec_id = (await admin.a_get_authentication_flow_executions(flow_alias="test-create"))[0]["id"] res = await admin.a_delete_authentication_flow_execution(execution_id=exec_id) @@ -6529,7 +6631,7 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str) -> None: flow_alias="test-create", skip_exists=True, ) - assert res == {"msg": "Already exists"} + assert res == json.dumps({"msg": "Already exists"}).encode() # Test delete auth flow flow_id = next( @@ -6603,13 +6705,14 @@ async def test_a_authentication_configs(admin: KeycloakAdmin, realm: str) -> Non # Test authenticator config executions = await admin.a_get_authentication_flow_executions(flow_alias="browser") execution = next(ex for ex in executions if ex["configurable"]) - _ = await admin.a_create_execution_config( + res = await admin.a_create_execution_config( execution["id"], { "alias": "test.provisioning.property", "config": {"test.provisioning.property": "value2"}, }, ) + assert res == b"" executions = await admin.a_get_authentication_flow_executions(flow_alias="browser") execution_config_id = next(ex for ex in executions if ex.get("id") == execution["id"])[ "authenticationConfig" @@ -6627,10 +6730,14 @@ async def test_a_authentication_configs(admin: KeycloakAdmin, realm: str) -> Non with pytest.raises(KeycloakPutError) as err: await admin.a_update_authenticator_config(payload={}, config_id="bad") assert err.match('404: b\'{"error":"Could not find authenticator config".*}\'') + res = await admin.a_update_authenticator_config(payload={}, config_id=execution_config_id) + assert res == {} with pytest.raises(KeycloakDeleteError) as err: await admin.a_delete_authenticator_config(config_id="bad") assert err.match('404: b\'{"error":"Could not find authenticator config".*}\'') + res = await admin.a_delete_authenticator_config(config_id=execution_config_id) + assert res == {} @pytest.mark.asyncio @@ -6970,12 +7077,14 @@ async def test_a_auto_refresh(admin_frozen: KeycloakAdmin, realm: str) -> None: assert res["realm"] == realm # Freeze time to simulate the access token expiring + assert admin.connection.expires_at is not None with freezegun.freeze_time("2023-02-25 10:05:00"): assert admin.connection.expires_at < datetime_parser.parse("2023-02-25T10:05:00Z") assert await admin.a_get_realm(realm_name=realm) assert admin.connection.expires_at > datetime_parser.parse("2023-02-25T10:05:00Z") # Test bad refresh token, but first make sure access token has expired again + assert admin.connection.token is not None with freezegun.freeze_time("2023-02-25 10:10:00"): admin.connection.custom_headers = {"Content-Type": "application/json"} admin.connection.token["refresh_token"] = "bad" # noqa: S105 @@ -7050,6 +7159,7 @@ async def test_a_get_required_action_by_alias(admin: KeycloakAdmin, realm: str) await admin.a_change_current_realm(realm) ractions = await admin.a_get_required_actions() ra = await admin.a_get_required_action_by_alias("UPDATE_PASSWORD") + assert ra is not None assert ra in ractions assert ra["alias"] == "UPDATE_PASSWORD" assert await admin.a_get_required_action_by_alias("does-not-exist") is None @@ -7068,9 +7178,12 @@ async def test_a_update_required_action(admin: KeycloakAdmin, realm: str) -> Non await admin.a_change_current_realm(realm) ra = await admin.a_get_required_action_by_alias("UPDATE_PASSWORD") old = copy.deepcopy(ra) + assert ra is not None ra["enabled"] = False - admin.update_required_action("UPDATE_PASSWORD", ra) + res = await admin.a_update_required_action("UPDATE_PASSWORD", ra) + assert res == {} newra = await admin.a_get_required_action_by_alias("UPDATE_PASSWORD") + assert newra is not None assert old != newra assert newra["enabled"] is False @@ -7198,7 +7311,9 @@ async def test_a_upload_certificate( await admin.a_change_current_realm(realm) cert, _ = selfsigned_cert cert = cert.decode("utf-8").strip() - admin.upload_certificate(client, cert) + res = await admin.a_upload_certificate(client, cert) + assert isinstance(res, dict) + assert "certificate" in res cl = await admin.a_get_client(client) assert cl["attributes"]["jwt.credential.certificate"] == "".join(cert.splitlines()[1:-1]) @@ -7232,6 +7347,7 @@ async def test_a_get_bruteforce_status_for_user( oid.token(username=username, password="wrongpassword") # noqa: S106 user_id = await admin.a_get_user_id(username) + assert user_id is not None bruteforce_status = await admin.a_get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 1 @@ -7271,10 +7387,12 @@ async def test_a_clear_bruteforce_attempts_for_user( oid.token(username=username, password="wrongpassword") # noqa: S106 user_id = await admin.a_get_user_id(username) + assert user_id is not None bruteforce_status = await admin.a_get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 1 res = await admin.a_clear_bruteforce_attempts_for_user(user_id) + assert res == {} bruteforce_status = await admin.a_get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 0 @@ -7313,10 +7431,12 @@ async def test_a_clear_bruteforce_attempts_for_all_users( oid.token(username=username, password="wrongpassword") # noqa: S106 user_id = await admin.a_get_user_id(username) + assert user_id is not None bruteforce_status = await admin.a_get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 1 res = await admin.a_clear_all_bruteforce_attempts() + assert res == {} bruteforce_status = await admin.a_get_bruteforce_detection_status(user_id) assert bruteforce_status["numFailures"] == 0 @@ -7512,7 +7632,10 @@ async def test_a_refresh_token(admin: KeycloakAdmin) -> None: """ admin.get_realms() assert admin.connection.token is not None - await admin.a_user_logout(await admin.a_get_user_id(admin.connection.username)) + assert admin.connection.username is not None + user_id = await admin.a_get_user_id(admin.connection.username) + assert user_id is not None + await admin.a_user_logout(user_id=user_id) admin.connection.refresh_token() diff --git a/tests/test_keycloak_openid.py b/tests/test_keycloak_openid.py index 712c312..40526c0 100644 --- a/tests/test_keycloak_openid.py +++ b/tests/test_keycloak_openid.py @@ -160,7 +160,7 @@ def test_token(oid_with_credentials: tuple[KeycloakOpenID, str, str]) -> None: } # Test with dummy totp - token = oid.token(username=username, password=password, totp="123456") + token = oid.token(username=username, password=password, totp=123456) assert token == { "access_token": mock.ANY, "expires_in": mock.ANY, @@ -205,15 +205,14 @@ def test_exchange_token( # Allow impersonation admin.change_current_realm(oid.realm_name) + user_id = admin.get_user_id(username=username) + assert user_id is not None + client_id = admin.get_client_id(client_id="realm-management") + assert client_id is not None admin.assign_client_role( - user_id=admin.get_user_id(username=username), - client_id=admin.get_client_id(client_id="realm-management"), - roles=[ - admin.get_client_role( - client_id=admin.get_client_id(client_id="realm-management"), - role_name="impersonation", - ), - ], + user_id=user_id, + client_id=client_id, + roles=[admin.get_client_role(client_id=client_id, role_name="impersonation")], ) token = oid.token(username=username, password=password) @@ -297,9 +296,9 @@ def test_entitlement( """ oid, username, password = oid_with_credentials_authz token = oid.token(username=username, password=password) - resource_server_id = admin.get_client_authz_resources( - client_id=admin.get_client_id(oid.client_id), - )[0]["_id"] + client_id = admin.get_client_id(oid.client_id) + assert client_id is not None + resource_server_id = admin.get_client_authz_resources(client_id=client_id)[0]["_id"] with pytest.raises(KeycloakDeprecationError): oid.entitlement(token=token["access_token"], resource_server_id=resource_server_id) @@ -431,11 +430,11 @@ def test_get_policies(oid_with_credentials_authz: tuple[KeycloakOpenID, str, str oid.authorization.policies["test"] = policy assert [ str(x) - for x in oid.get_policies(token=token["access_token"], method_token_info="decode") # noqa: S106 + for x in (oid.get_policies(token=token["access_token"], method_token_info="decode") or []) # noqa: S106 ] == ["Policy: test (role)"] assert [ repr(x) - for x in oid.get_policies(token=token["access_token"], method_token_info="decode") # noqa: S106 + for x in (oid.get_policies(token=token["access_token"], method_token_info="decode") or []) # noqa: S106 ] == [""] oid.client_id = orig_client_id @@ -477,11 +476,15 @@ 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") # noqa: S106 + for x in ( + oid.get_permissions(token=token["access_token"], method_token_info="decode") or [] # noqa: S106 + ) ] == ["Permission: test-perm (resource)"] assert [ repr(x) - for x in oid.get_permissions(token=token["access_token"], method_token_info="decode") # noqa: S106 + for x in ( + oid.get_permissions(token=token["access_token"], method_token_info="decode") or [] # noqa: S106 + ) ] == [""] oid.client_id = orig_client_id @@ -538,6 +541,7 @@ def test_has_uma_access( str(oid.has_uma_access(token=token["access_token"], permissions="")) == "AuthStatus(is_authorized=False, is_logged_in=False, missing_permissions=set())" ) + assert admin.connection.token is not None assert ( str( oid.has_uma_access( @@ -684,7 +688,7 @@ async def test_a_token(oid_with_credentials: tuple[KeycloakOpenID, str, str]) -> } # Test with dummy totp - token = await oid.a_token(username=username, password=password, totp="123456") + token = await oid.a_token(username=username, password=password, totp=123456) assert token == { "access_token": mock.ANY, "expires_in": mock.ANY, @@ -730,14 +734,15 @@ async def test_a_exchange_token( # Allow impersonation await admin.a_change_current_realm(oid.realm_name) + user_id = await admin.a_get_user_id(username=username) + assert user_id is not None + client_id = await admin.a_get_client_id(client_id="realm-management") + assert client_id is not None await admin.a_assign_client_role( - user_id=await admin.a_get_user_id(username=username), - client_id=await admin.a_get_client_id(client_id="realm-management"), + user_id=user_id, + client_id=client_id, roles=[ - await admin.a_get_client_role( - client_id=admin.get_client_id(client_id="realm-management"), - role_name="impersonation", - ), + await admin.a_get_client_role(client_id=client_id, role_name="impersonation"), ], ) @@ -826,9 +831,9 @@ async def test_a_entitlement( """ oid, username, password = oid_with_credentials_authz token = await oid.a_token(username=username, password=password) - resource_server_id = admin.get_client_authz_resources( - client_id=admin.get_client_id(oid.client_id), - )[0]["_id"] + client_id = await admin.a_get_client_id(oid.client_id) + assert client_id is not None + resource_server_id = admin.get_client_authz_resources(client_id=client_id)[0]["_id"] with pytest.raises(KeycloakDeprecationError): await oid.a_entitlement(token=token["access_token"], resource_server_id=resource_server_id) @@ -989,6 +994,7 @@ async def test_a_has_uma_access( str(await oid.a_has_uma_access(token=token["access_token"], permissions="")) == "AuthStatus(is_authorized=False, is_logged_in=False, missing_permissions=set())" ) + assert admin.connection.token is not None assert ( str( await oid.a_has_uma_access( @@ -1027,11 +1033,15 @@ async def test_a_get_policies(oid_with_credentials_authz: tuple[KeycloakOpenID, oid.authorization.policies["test"] = policy assert [ str(x) - for x in await oid.a_get_policies(token=token["access_token"], method_token_info="decode") # noqa: S106 + for x in ( + await oid.a_get_policies(token=token["access_token"], method_token_info="decode") or [] # noqa: S106 + ) ] == ["Policy: test (role)"] assert [ repr(x) - for x in await oid.a_get_policies(token=token["access_token"], method_token_info="decode") # noqa: S106 + for x in ( + await oid.a_get_policies(token=token["access_token"], method_token_info="decode") or [] # noqa: S106 + ) ] == [""] oid.client_id = orig_client_id @@ -1078,16 +1088,22 @@ async def test_a_get_permissions( oid.authorization.policies["test"] = policy assert [ str(x) - for x in await oid.a_get_permissions( - token=token["access_token"], - method_token_info="decode", # noqa: S106 + for x in ( + await oid.a_get_permissions( + token=token["access_token"], + method_token_info="decode", # noqa: S106 + ) + or [] ) ] == ["Permission: test-perm (resource)"] assert [ repr(x) - for x in await oid.a_get_permissions( - token=token["access_token"], - method_token_info="decode", # noqa: S106 + for x in ( + await oid.a_get_permissions( + token=token["access_token"], + method_token_info="decode", # noqa: S106 + ) + or [] ) ] == [""] oid.client_id = orig_client_id diff --git a/tests/test_keycloak_uma.py b/tests/test_keycloak_uma.py index ca49364..400587f 100644 --- a/tests/test_keycloak_uma.py +++ b/tests/test_keycloak_uma.py @@ -216,7 +216,8 @@ def test_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin) -> None: assert len(policies) == 1 policy_id = policy["id"] - uma.policy_delete(policy_id) + policy_delete_res = uma.policy_delete(policy_id) + assert policy_delete_res == {} with pytest.raises(KeycloakDeleteError) as err: uma.policy_delete(policy_id) assert err.match( @@ -228,7 +229,8 @@ def test_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin) -> None: assert len(policies) == 2 policy = policies[0] - uma.policy_update(policy_id=policy["id"], payload=policy) + policy_update_res = uma.policy_update(policy_id=policy["id"], payload=policy) + assert policy_update_res == b"" policies = uma.policy_query() assert len(policies) == 2 @@ -254,6 +256,7 @@ def test_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin) -> None: uma.resource_set_delete(resource_id) admin.delete_client(other_client_id) admin.delete_realm_role(role_id) + assert group_id is not None admin.delete_group(group_id) @@ -282,6 +285,7 @@ def test_uma_access(uma: KeycloakUMA) -> None: token = uma.connection.token permissions = [] + assert token is not None assert uma.permissions_check(token["access_token"], permissions) permissions.append(UMAPermission(resource=resource_to_create["name"])) @@ -522,7 +526,8 @@ async def test_a_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin) -> None: assert len(policies) == 1 policy_id = policy["id"] - await uma.a_policy_delete(policy_id) + policy_delete_res = await uma.a_policy_delete(policy_id) + assert policy_delete_res == {} with pytest.raises(KeycloakDeleteError) as err: await uma.a_policy_delete(policy_id) assert err.match( @@ -534,7 +539,8 @@ async def test_a_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin) -> None: assert len(policies) == 2 policy = policies[0] - await uma.a_policy_update(policy_id=policy["id"], payload=policy) + policy_update_res = await uma.a_policy_update(policy_id=policy["id"], payload=policy) + assert policy_update_res == b"" policies = await uma.a_policy_query() assert len(policies) == 2 @@ -560,6 +566,7 @@ async def test_a_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin) -> None: await uma.a_resource_set_delete(resource_id) await admin.a_delete_client(other_client_id) await admin.a_delete_realm_role(role_id) + assert group_id is not None await admin.a_delete_group(group_id) @@ -589,6 +596,7 @@ async def test_a_uma_access(uma: KeycloakUMA) -> None: token = uma.connection.token permissions = [] + assert token is not None assert await uma.a_permissions_check(token["access_token"], permissions) permissions.append(UMAPermission(resource=resource_to_create["name"])) diff --git a/tests/test_pkce_flow.py b/tests/test_pkce_flow.py index 5e411fd..5fea924 100644 --- a/tests/test_pkce_flow.py +++ b/tests/test_pkce_flow.py @@ -9,9 +9,10 @@ from packaging.version import Version from keycloak import KeycloakAdmin, KeycloakOpenID from keycloak.pkce_utils import generate_code_challenge, generate_code_verifier +from tests.conftest import KeycloakTestEnv -def test_pkce_auth_url_and_token(env: object, admin: KeycloakAdmin) -> None: +def test_pkce_auth_url_and_token(env: KeycloakTestEnv, admin: KeycloakAdmin) -> None: """Test PKCE flow: auth_url includes code_challenge, token includes code_verifier.""" if os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"] != "latest" and Version( os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"], @@ -74,4 +75,5 @@ def test_pkce_auth_url_and_token(env: object, admin: KeycloakAdmin) -> None: # Cleanup client_id = admin.get_client_id("pkce-test") + assert client_id is not None admin.delete_client(client_id) diff --git a/tests/test_uma_permissions.py b/tests/test_uma_permissions.py index 881854f..33bb866 100644 --- a/tests/test_uma_permissions.py +++ b/tests/test_uma_permissions.py @@ -20,7 +20,7 @@ import re import pytest -from keycloak.exceptions import KeycloakPermissionFormatError, PermissionDefinitionError +from keycloak.exceptions import KeycloakPermissionFormatError from keycloak.uma_permissions import ( AuthStatus, Resource, @@ -32,9 +32,6 @@ from keycloak.uma_permissions import ( def test_uma_permission_obj() -> None: """Test generic UMA permission.""" - with pytest.raises(PermissionDefinitionError): - UMAPermission(permission="bad") - p1 = UMAPermission(permission=Resource("Resource")) assert p1.resource == "Resource" assert p1.scope == "" @@ -77,15 +74,6 @@ def test_scope_resource_str() -> None: assert s(resource=r) == "Resource1#Scope1" -def test_resource_scope_list() -> None: - """Test resource scope as list.""" - r = Resource("Resource1") - s = ["Scope1"] - with pytest.raises(PermissionDefinitionError) as err: - r(s) - assert err.match(re.escape("can't determine if '['Scope1']' is a resource or scope")) - - def test_build_permission_none() -> None: """Test build permission param with None.""" assert build_permission_param(None) == set() @@ -172,27 +160,6 @@ def test_build_permission_misbuilt_dict_str_list_list_str() -> None: assert err.match(re.escape("misbuilt permission {'res1': [['scope1', 'scope2']]}")) -def test_build_permission_misbuilt_list_list_str() -> None: - """Test bad build of permission param from list.""" - with pytest.raises(KeycloakPermissionFormatError) as err: - build_permission_param([["scope1", "scope2"]]) - assert err.match(re.escape("misbuilt permission [['scope1', 'scope2']]")) - - -def test_build_permission_misbuilt_list_set_str() -> None: - """Test bad build of permission param from set.""" - with pytest.raises(KeycloakPermissionFormatError) as err: - build_permission_param([{"scope1", "scope2"}]) - assert err.match("misbuilt permission.*") - - -def test_build_permission_misbuilt_set_set_str() -> None: - """Test bad build of permission param from list of set.""" - with pytest.raises(KeycloakPermissionFormatError) as err: - build_permission_param([{"scope1"}]) - assert err.match(re.escape("misbuilt permission [{'scope1'}]")) - - def test_build_permission_misbuilt_dict_non_iterable() -> None: """Test bad build of permission param from non-iterable.""" with pytest.raises(KeycloakPermissionFormatError) as err: