Browse Source

Merge pull request #373 from marcospereirampj/style/more_checks

Style/more checks
pull/374/head
Richard Nemeth 2 years ago
committed by GitHub
parent
commit
a34054d089
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      docs/source/changelog.rst
  2. 1
      docs/source/conf.py
  3. 595
      poetry.lock
  4. 5
      pyproject.toml
  5. 8
      src/keycloak/authorization/__init__.py
  6. 69
      src/keycloak/authorization/permission.py
  7. 78
      src/keycloak/authorization/policy.py
  8. 27
      src/keycloak/authorization/role.py
  9. 115
      src/keycloak/connection.py
  10. 34
      src/keycloak/exceptions.py
  11. 767
      src/keycloak/keycloak_admin.py
  12. 220
      src/keycloak/keycloak_openid.py
  13. 96
      src/keycloak/uma_permissions.py
  14. 180
      tests/conftest.py
  15. 297
      tests/test_keycloak_admin.py
  16. 110
      tests/test_keycloak_openid.py
  17. 7
      tox.ini

1
docs/source/changelog.rst

@ -1,2 +1 @@
.. mdinclude:: ../../CHANGELOG.md .. mdinclude:: ../../CHANGELOG.md

1
docs/source/conf.py

@ -86,6 +86,7 @@ language = "en"
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path # This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
suppress_warnings = ["ref.python"]
add_function_parentheses = False add_function_parentheses = False
add_module_names = True add_module_names = True

595
poetry.lock

@ -117,17 +117,9 @@ category = "dev"
optional = false optional = false
python-versions = ">=3.6.1" python-versions = ">=3.6.1"
[[package]]
name = "chardet"
version = "5.0.0"
description = "Universal encoding detector for Python 3"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "2.1.0"
version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main" category = "main"
optional = false optional = false
@ -148,6 +140,18 @@ python-versions = ">=3.7"
colorama = {version = "*", markers = "platform_system == \"Windows\""} colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "codespell"
version = "2.2.1"
description = "Codespell"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
dev = ["check-manifest", "flake8", "pytest", "pytest-cov", "pytest-dependency"]
hard-encoding-detection = ["chardet"]
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.5" version = "0.4.5"
@ -158,7 +162,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "commitizen" name = "commitizen"
version = "2.29.2"
version = "2.32.2"
description = "Python commitizen client tool" description = "Python commitizen client tool"
category = "dev" category = "dev"
optional = false optional = false
@ -166,7 +170,7 @@ python-versions = ">=3.6.2,<4.0.0"
[package.dependencies] [package.dependencies]
argcomplete = ">=1.12.1,<2.0.0" argcomplete = ">=1.12.1,<2.0.0"
chardet = ">=5.0.0,<6.0.0"
charset-normalizer = ">=2.1.0,<3.0.0"
colorama = ">=0.4.1,<0.5.0" colorama = ">=0.4.1,<0.5.0"
decli = ">=0.5.2,<0.6.0" decli = ">=0.5.2,<0.6.0"
jinja2 = ">=2.10.3" jinja2 = ">=2.10.3"
@ -186,11 +190,11 @@ optional = true
python-versions = "*" python-versions = "*"
[package.extras] [package.extras]
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
test = ["hypothesis (==3.55.3)", "flake8 (==3.7.8)"]
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "6.4.2"
version = "6.4.4"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
category = "dev" category = "dev"
optional = false optional = false
@ -221,6 +225,14 @@ sdist = ["setuptools_rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"] ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "darglint"
version = "1.8.1"
description = "A utility for ensuring Google-style docstrings stay up to date with the source code."
category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
[[package]] [[package]]
name = "decli" name = "decli"
version = "0.5.2" version = "0.5.2"
@ -231,7 +243,7 @@ python-versions = ">=3.6"
[[package]] [[package]]
name = "distlib" name = "distlib"
version = "0.3.5"
version = "0.3.6"
description = "Distribution utilities" description = "Distribution utilities"
category = "dev" category = "dev"
optional = false optional = false
@ -262,15 +274,15 @@ gmpy2 = ["gmpy2"]
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.7.1"
version = "3.8.0"
description = "A platform independent file lock." description = "A platform independent file lock."
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[package.extras] [package.extras]
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
[[package]] [[package]]
name = "flake8" name = "flake8"
@ -300,7 +312,7 @@ pydocstyle = ">=2.1"
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.5.2"
version = "2.5.3"
description = "File identification library for Python" description = "File identification library for Python"
category = "dev" category = "dev"
optional = false optional = false
@ -494,8 +506,8 @@ python-versions = ">=3.6"
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras] [package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
testing = ["pytest-benchmark", "pytest"]
dev = ["tox", "pre-commit"]
[[package]] [[package]]
name = "pre-commit" name = "pre-commit"
@ -581,12 +593,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.12.0"
version = "2.13.0"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
category = "main" category = "main"
optional = true optional = true
python-versions = ">=3.6" python-versions = ">=3.6"
[package.extras]
plugins = ["importlib-metadata"]
[[package]] [[package]]
name = "pyparsing" name = "pyparsing"
version = "3.0.9" version = "3.0.9"
@ -633,7 +648,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6" pytest = ">=4.6"
[package.extras] [package.extras]
testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"]
[[package]] [[package]]
name = "python-jose" name = "python-jose"
@ -655,7 +670,7 @@ pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)", "pyasn1"]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2022.1"
version = "2022.2.1"
description = "World timezone definitions, modern and historical" description = "World timezone definitions, modern and historical"
category = "main" category = "main"
optional = true optional = true
@ -830,7 +845,7 @@ docutils = "<0.18"
sphinx = ">=1.6" sphinx = ">=1.6"
[package.extras] [package.extras]
dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"]
dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"]
[[package]] [[package]]
name = "sphinxcontrib-applehelp" name = "sphinxcontrib-applehelp"
@ -841,8 +856,8 @@ optional = true
python-versions = ">=3.5" python-versions = ">=3.5"
[package.extras] [package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"] test = ["pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]] [[package]]
name = "sphinxcontrib-devhelp" name = "sphinxcontrib-devhelp"
@ -853,8 +868,8 @@ optional = true
python-versions = ">=3.5" python-versions = ">=3.5"
[package.extras] [package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"] test = ["pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]] [[package]]
name = "sphinxcontrib-htmlhelp" name = "sphinxcontrib-htmlhelp"
@ -865,8 +880,8 @@ optional = true
python-versions = ">=3.6" python-versions = ">=3.6"
[package.extras] [package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest", "html5lib"]
test = ["html5lib", "pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]] [[package]]
name = "sphinxcontrib-jsmath" name = "sphinxcontrib-jsmath"
@ -877,7 +892,7 @@ optional = true
python-versions = ">=3.5" python-versions = ">=3.5"
[package.extras] [package.extras]
test = ["pytest", "flake8", "mypy"]
test = ["mypy", "flake8", "pytest"]
[[package]] [[package]]
name = "sphinxcontrib-qthelp" name = "sphinxcontrib-qthelp"
@ -888,8 +903,8 @@ optional = true
python-versions = ">=3.5" python-versions = ">=3.5"
[package.extras] [package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"] test = ["pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]] [[package]]
name = "sphinxcontrib-serializinghtml" name = "sphinxcontrib-serializinghtml"
@ -900,8 +915,8 @@ optional = true
python-versions = ">=3.5" python-versions = ">=3.5"
[package.extras] [package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"] test = ["pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
@ -929,7 +944,7 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "tomlkit" name = "tomlkit"
version = "0.11.1"
version = "0.11.4"
description = "Style preserving TOML library" description = "Style preserving TOML library"
category = "dev" category = "dev"
optional = false optional = false
@ -984,7 +999,7 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "1.26.11"
version = "1.26.12"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main" category = "main"
optional = false optional = false
@ -992,26 +1007,26 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*,
[package.extras] [package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.16.2"
version = "20.16.3"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
[package.dependencies] [package.dependencies]
distlib = ">=0.3.1,<1"
filelock = ">=3.2,<4"
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
platformdirs = ">=2,<3"
distlib = ">=0.3.5,<1"
filelock = ">=3.4.1,<4"
importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""}
platformdirs = ">=2.4,<3"
[package.extras] [package.extras]
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"]
docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
[[package]] [[package]]
name = "wcwidth" name = "wcwidth"
@ -1047,470 +1062,96 @@ docs = ["mock", "alabaster", "commonmark", "recommonmark", "Sphinx", "sphinx-rtd
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "b6851001fb6b3e331a39e6bab1fa2ed99fdc555e9683137c20c9c593c0e1c040"
content-hash = "5d740e81a3604cb20ca85945c2fc134f6373f599315992afb50284f1f993c1d0"
[metadata.files] [metadata.files]
alabaster = [
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
]
argcomplete = [
{file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"},
{file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"},
]
alabaster = []
argcomplete = []
astroid = [] astroid = []
atomicwrites = [] atomicwrites = []
attrs = [] attrs = []
babel = [
{file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"},
{file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"},
]
babel = []
black = [] black = []
certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
certifi = []
cffi = [] cffi = []
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
chardet = []
cfgv = []
charset-normalizer = [] charset-normalizer = []
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
click = []
codespell = []
colorama = []
commitizen = [] commitizen = []
commonmark = [
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
]
commonmark = []
coverage = [] coverage = []
cryptography = [] cryptography = []
decli = [
{file = "decli-0.5.2-py3-none-any.whl", hash = "sha256:d3207bc02d0169bf6ed74ccca09ce62edca0eb25b0ebf8bf4ae3fb8333e15ca0"},
{file = "decli-0.5.2.tar.gz", hash = "sha256:f2cde55034a75c819c630c7655a844c612f2598c42c21299160465df6ad463ad"},
]
darglint = []
decli = []
distlib = [] distlib = []
docutils = [
{file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
{file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
]
docutils = []
ecdsa = [] ecdsa = []
filelock = [
{file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"},
{file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"},
]
flake8 = [
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
]
filelock = []
flake8 = []
flake8-docstrings = [] flake8-docstrings = []
identify = [] identify = []
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
idna = []
imagesize = [] imagesize = []
importlib-metadata = [
{file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
{file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
jinja2 = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
lazy-object-proxy = [
{file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"},
{file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
]
m2r2 = [
{file = "m2r2-0.3.2-py3-none-any.whl", hash = "sha256:d3684086b61b4bebe2307f15189495360f05a123c9bda2a66462649b7ca236aa"},
{file = "m2r2-0.3.2.tar.gz", hash = "sha256:ccd95b052dcd1ac7442ecb3111262b2001c10e4119b459c34c93ac7a5c2c7868"},
]
markupsafe = [
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
mistune = [
{file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
{file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
]
mock = [
{file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"},
{file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
nodeenv = [
{file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
{file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
importlib-metadata = []
iniconfig = []
isort = []
jinja2 = []
lazy-object-proxy = []
m2r2 = []
markupsafe = []
mccabe = []
mistune = []
mock = []
mypy-extensions = []
nodeenv = []
packaging = []
pathspec = []
platformdirs = []
pluggy = []
pre-commit = [] pre-commit = []
prompt-toolkit = [] prompt-toolkit = []
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pyasn1 = [
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
{file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
{file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
{file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
{file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
{file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
{file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
{file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
{file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
]
pycodestyle = [
{file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
{file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
]
py = []
pyasn1 = []
pycodestyle = []
pycparser = [] pycparser = []
pydocstyle = [] pydocstyle = []
pyflakes = [
{file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
]
pygments = [
{file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"},
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pytest = [
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
]
pytest-cov = [
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
]
python-jose = [
{file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
{file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
]
pytz = [
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
]
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
questionary = [
{file = "questionary-1.10.0-py3-none-any.whl", hash = "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90"},
{file = "questionary-1.10.0.tar.gz", hash = "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90"},
]
readthedocs-sphinx-ext = [
{file = "readthedocs-sphinx-ext-2.1.8.tar.gz", hash = "sha256:a57e3713daf77bf91d1ba19e4b9888a47c0abfeb63ecf02e3ac77fcfd99bfe69"},
{file = "readthedocs_sphinx_ext-2.1.8-py2.py3-none-any.whl", hash = "sha256:5ab5875993191e5e526ca196a1082b73116b0cefd79073ab25367ba0458fffe9"},
]
recommonmark = [
{file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"},
{file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"},
]
pyflakes = []
pygments = []
pyparsing = []
pytest = []
pytest-cov = []
python-jose = []
pytz = []
pyyaml = []
questionary = []
readthedocs-sphinx-ext = []
recommonmark = []
requests = [] requests = []
requests-toolbelt = [] requests-toolbelt = []
rsa = [] rsa = []
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
snowballstemmer = [
{file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
six = []
snowballstemmer = []
sphinx = [] sphinx = []
sphinx-autoapi = [] sphinx-autoapi = []
sphinx-rtd-theme = [
{file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"},
{file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"},
]
sphinxcontrib-applehelp = [
{file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
{file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
]
sphinxcontrib-devhelp = [
{file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
{file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
]
sphinxcontrib-htmlhelp = [
{file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"},
{file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"},
]
sphinxcontrib-jsmath = [
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
]
sphinxcontrib-qthelp = [
{file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
{file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
]
sphinxcontrib-serializinghtml = [
{file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
{file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
]
termcolor = [
{file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
sphinx-rtd-theme = []
sphinxcontrib-applehelp = []
sphinxcontrib-devhelp = []
sphinxcontrib-htmlhelp = []
sphinxcontrib-jsmath = []
sphinxcontrib-qthelp = []
sphinxcontrib-serializinghtml = []
termcolor = []
toml = []
tomli = []
tomlkit = [] tomlkit = []
tox = [] tox = []
typed-ast = [
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
typed-ast = []
typing-extensions = [] typing-extensions = []
unidecode = [
{file = "Unidecode-1.3.4-py3-none-any.whl", hash = "sha256:afa04efcdd818a93237574791be9b2817d7077c25a068b00f8cff7baa4e59257"},
{file = "Unidecode-1.3.4.tar.gz", hash = "sha256:8e4352fb93d5a735c788110d2e7ac8e8031eb06ccbfe8d324ab71735015f9342"},
]
unidecode = []
urllib3 = [] urllib3 = []
virtualenv = [] virtualenv = []
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]
wrapt = [
{file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
{file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
{file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
{file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
{file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
{file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
{file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
{file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
{file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
{file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
{file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
{file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
{file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
{file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
{file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
{file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
{file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
{file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
{file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
{file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
{file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
{file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
]
wcwidth = []
wrapt = []
zipp = [] zipp = []

5
pyproject.toml

@ -56,6 +56,8 @@ flake8 = "^3.5.0"
flake8-docstrings = "^1.6.0" flake8-docstrings = "^1.6.0"
commitizen = "^2.28.0" commitizen = "^2.28.0"
cryptography = "^37.0.4" cryptography = "^37.0.4"
codespell = "^2.1.0"
darglint = "^1.8.1"
[tool.poetry.extras] [tool.poetry.extras]
docs = [ docs = [
@ -80,3 +82,6 @@ line-length = 99
[tool.isort] [tool.isort]
line_length = 99 line_length = 99
profile = "black" profile = "black"
[tool.darglint]
enable = "DAR104"

8
src/keycloak/authorization/__init__.py

@ -44,7 +44,11 @@ class Authorization:
@property @property
def policies(self): def policies(self):
"""Get policies."""
"""Get policies.
:returns: Policies
:rtype: dict
"""
return self._policies return self._policies
@policies.setter @policies.setter
@ -55,7 +59,7 @@ class Authorization:
"""Load policies, roles and permissions (scope/resources). """Load policies, roles and permissions (scope/resources).
:param data: keycloak authorization data (dict) :param data: keycloak authorization data (dict)
:returns: None
:type data: dict
""" """
for pol in data["policies"]: for pol in data["policies"]:
if pol["type"] == "role": if pol["type"] == "role":

69
src/keycloak/authorization/permission.py

@ -45,10 +45,29 @@ class Permission:
https://keycloak.gitbooks.io/documentation/authorization_services/topics/permission/overview.html https://keycloak.gitbooks.io/documentation/authorization_services/topics/permission/overview.html
:param name: Name
:type name: str
:param type: Type
:type type: str
:param logic: Logic
:type logic: str
:param decision_strategy: Decision strategy
:type decision_strategy: str
""" """
def __init__(self, name, type, logic, decision_strategy): def __init__(self, name, type, logic, decision_strategy):
"""Init method."""
"""Init method.
:param name: Name
:type name: str
:param type: Type
:type type: str
:param logic: Logic
:type logic: str
:param decision_strategy: Decision strategy
:type decision_strategy: str
"""
self.name = name self.name = name
self.type = type self.type = type
self.logic = logic self.logic = logic
@ -57,16 +76,28 @@ class Permission:
self.scopes = [] self.scopes = []
def __repr__(self): def __repr__(self):
"""Repr method."""
"""Repr method.
:returns: Class representation
:rtype: str
"""
return "<Permission: %s (%s)>" % (self.name, self.type) return "<Permission: %s (%s)>" % (self.name, self.type)
def __str__(self): def __str__(self):
"""Str method."""
"""Str method.
:returns: Class string representation
:rtype: str
"""
return "Permission: %s (%s)" % (self.name, self.type) return "Permission: %s (%s)" % (self.name, self.type)
@property @property
def name(self): def name(self):
"""Get name."""
"""Get name.
:returns: name
:rtype: str
"""
return self._name return self._name
@name.setter @name.setter
@ -75,7 +106,11 @@ class Permission:
@property @property
def type(self): def type(self):
"""Get type."""
"""Get type.
:returns: type
:rtype: str
"""
return self._type return self._type
@type.setter @type.setter
@ -84,7 +119,11 @@ class Permission:
@property @property
def logic(self): def logic(self):
"""Get logic."""
"""Get logic.
:returns: Logic
:rtype: str
"""
return self._logic return self._logic
@logic.setter @logic.setter
@ -93,7 +132,11 @@ class Permission:
@property @property
def decision_strategy(self): def decision_strategy(self):
"""Get decision strategy."""
"""Get decision strategy.
:returns: Decision strategy
:rtype: str
"""
return self._decision_strategy return self._decision_strategy
@decision_strategy.setter @decision_strategy.setter
@ -102,7 +145,11 @@ class Permission:
@property @property
def resources(self): def resources(self):
"""Get resources."""
"""Get resources.
:returns: Resources
:rtype: list
"""
return self._resources return self._resources
@resources.setter @resources.setter
@ -111,7 +158,11 @@ class Permission:
@property @property
def scopes(self): def scopes(self):
"""Get scopes."""
"""Get scopes.
:returns: Scopes
:rtype: list
"""
return self._scopes return self._scopes
@scopes.setter @scopes.setter

78
src/keycloak/authorization/policy.py

@ -39,10 +39,29 @@ class Policy:
https://keycloak.gitbooks.io/documentation/authorization_services/topics/policy/overview.html https://keycloak.gitbooks.io/documentation/authorization_services/topics/policy/overview.html
:param name: Name
:type name: str
:param type: Type
:type type: str
:param logic: Logic
:type logic: str
:param decision_strategy: Decision strategy
:type decision_strategy: str
""" """
def __init__(self, name, type, logic, decision_strategy): def __init__(self, name, type, logic, decision_strategy):
"""Init method."""
"""Init method.
:param name: Name
:type name: str
:param type: Type
:type type: str
:param logic: Logic
:type logic: str
:param decision_strategy: Decision strategy
:type decision_strategy: str
"""
self.name = name self.name = name
self.type = type self.type = type
self.logic = logic self.logic = logic
@ -51,16 +70,28 @@ class Policy:
self.permissions = [] self.permissions = []
def __repr__(self): def __repr__(self):
"""Repr method."""
"""Repr method.
:returns: Class representation
:rtype: str
"""
return "<Policy: %s (%s)>" % (self.name, self.type) return "<Policy: %s (%s)>" % (self.name, self.type)
def __str__(self): def __str__(self):
"""Str method."""
"""Str method.
:returns: Class string representation
:rtype: str
"""
return "Policy: %s (%s)" % (self.name, self.type) return "Policy: %s (%s)" % (self.name, self.type)
@property @property
def name(self): def name(self):
"""Get name."""
"""Get name.
:returns: Name
:rtype: str
"""
return self._name return self._name
@name.setter @name.setter
@ -69,7 +100,11 @@ class Policy:
@property @property
def type(self): def type(self):
"""Get type."""
"""Get type.
:returns: Type
:rtype: str
"""
return self._type return self._type
@type.setter @type.setter
@ -78,7 +113,11 @@ class Policy:
@property @property
def logic(self): def logic(self):
"""Get logic."""
"""Get logic.
:returns: Logic
:rtype: str
"""
return self._logic return self._logic
@logic.setter @logic.setter
@ -87,7 +126,11 @@ class Policy:
@property @property
def decision_strategy(self): def decision_strategy(self):
"""Get decision strategy."""
"""Get decision strategy.
:returns: Decision strategy
:rtype: str
"""
return self._decision_strategy return self._decision_strategy
@decision_strategy.setter @decision_strategy.setter
@ -96,7 +139,11 @@ class Policy:
@property @property
def roles(self): def roles(self):
"""Get roles."""
"""Get roles.
:returns: Roles
:rtype: list
"""
return self._roles return self._roles
@roles.setter @roles.setter
@ -105,7 +152,11 @@ class Policy:
@property @property
def permissions(self): def permissions(self):
"""Get permissions."""
"""Get permissions.
:returns: Permissions
:rtype: list
"""
return self._permissions return self._permissions
@permissions.setter @permissions.setter
@ -115,8 +166,9 @@ class Policy:
def add_role(self, role): def add_role(self, role):
"""Add keycloak role in policy. """Add keycloak role in policy.
:param role: keycloak role.
:return:
:param role: Keycloak role
:type role: keycloak.authorization.Role
:raises KeycloakAuthorizationConfigError: In case of misconfigured policy type
""" """
if self.type != "role": if self.type != "role":
raise KeycloakAuthorizationConfigError( raise KeycloakAuthorizationConfigError(
@ -127,7 +179,7 @@ class Policy:
def add_permission(self, permission): def add_permission(self, permission):
"""Add keycloak permission in policy. """Add keycloak permission in policy.
:param permission: keycloak permission.
:return:
:param permission: Keycloak permission
:type permission: keycloak.authorization.Permission
""" """
self._permissions.append(permission) self._permissions.append(permission)

27
src/keycloak/authorization/role.py

@ -31,19 +31,40 @@ class Role:
manager, and employee are all typical roles that may exist in an organization. manager, and employee are all typical roles that may exist in an organization.
https://keycloak.gitbooks.io/documentation/server_admin/topics/roles.html https://keycloak.gitbooks.io/documentation/server_admin/topics/roles.html
:param name: Name
:type name: str
:param required: Required role indicator
:type required: bool
""" """
def __init__(self, name, required=False): def __init__(self, name, required=False):
"""Init method."""
"""Init method.
:param name: Name
:type name: str
:param required: Required role indicator
:type required: bool
"""
self.name = name self.name = name
self.required = required self.required = required
def get_name(self): def get_name(self):
"""Get name."""
"""Get name.
:returns: Name
:rtype: str
"""
return self.name return self.name
def __eq__(self, other): def __eq__(self, other):
"""Eq method."""
"""Eq method.
:param other: The other object
:type other: str
:returns: Equality bool
:rtype: bool | NotImplemented
"""
if isinstance(other, str): if isinstance(other, str):
return self.name == other return self.name == other
return NotImplemented return NotImplemented

115
src/keycloak/connection.py

@ -37,15 +37,32 @@ from .exceptions import KeycloakConnectionError
class ConnectionManager(object): class ConnectionManager(object):
"""Represents a simple server connection. """Represents a simple server connection.
:param base_url: (str) The server URL.
:param headers: (dict) The header parameters of the requests to the server.
:param timeout: (int) Timeout to use for requests to the server.
:param verify: (bool) Verify server SSL.
:param proxies: (dict) The proxies servers requests is sent by.
:param base_url: The server URL.
:type base_url: str
:param headers: The header parameters of the requests to the server.
:type headers: dict
:param timeout: Timeout to use for requests to the server.
:type timeout: int
:param verify: Verify server SSL.
:type verify: bool
:param proxies: The proxies servers requests is sent by.
:type proxies: dict
""" """
def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None): def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None):
"""Init method."""
"""Init method.
:param base_url: The server URL.
:type base_url: str
:param headers: The header parameters of the requests to the server.
:type headers: dict
:param timeout: Timeout to use for requests to the server.
:type timeout: int
:param verify: Verify server SSL.
:type verify: bool
:param proxies: The proxies servers requests is sent by.
:type proxies: dict
"""
self.base_url = base_url self.base_url = base_url
self.headers = headers self.headers = headers
self.timeout = timeout self.timeout = timeout
@ -73,7 +90,11 @@ class ConnectionManager(object):
@property @property
def base_url(self): def base_url(self):
"""Return base url in use for requests to the server."""
"""Return base url in use for requests to the server.
:returns: Base URL
:rtype: str
"""
return self._base_url return self._base_url
@base_url.setter @base_url.setter
@ -82,7 +103,11 @@ class ConnectionManager(object):
@property @property
def timeout(self): def timeout(self):
"""Return timeout in use for request to the server."""
"""Return timeout in use for request to the server.
:returns: Timeout
:rtype: int
"""
return self._timeout return self._timeout
@timeout.setter @timeout.setter
@ -91,7 +116,11 @@ class ConnectionManager(object):
@property @property
def verify(self): def verify(self):
"""Return verify in use for request to the server."""
"""Return verify in use for request to the server.
:returns: Verify indicator
:rtype: bool
"""
return self._verify return self._verify
@verify.setter @verify.setter
@ -100,7 +129,11 @@ class ConnectionManager(object):
@property @property
def headers(self): def headers(self):
"""Return header request to the server."""
"""Return header request to the server.
:returns: Request headers
:rtype: dict
"""
return self._headers return self._headers
@headers.setter @headers.setter
@ -110,8 +143,10 @@ class ConnectionManager(object):
def param_headers(self, key): def param_headers(self, key):
"""Return a specific header parameter. """Return a specific header parameter.
:param key: (str) Header parameters key.
:param key: Header parameters key.
:type key: str
:returns: If the header parameters exist, return its value. :returns: If the header parameters exist, return its value.
:rtype: str
""" """
return self.headers.get(key) return self.headers.get(key)
@ -122,32 +157,41 @@ class ConnectionManager(object):
def exist_param_headers(self, key): def exist_param_headers(self, key):
"""Check if the parameter exists in the header. """Check if the parameter exists in the header.
:param key: (str) Header parameters key.
:param key: Header parameters key.
:type key: str
:returns: If the header parameters exist, return True. :returns: If the header parameters exist, return True.
:rtype: bool
""" """
return self.param_headers(key) is not None return self.param_headers(key) is not None
def add_param_headers(self, key, value): def add_param_headers(self, key, value):
"""Add a single parameter inside the header. """Add a single parameter inside the header.
:param key: (str) Header parameters key.
:param value: (str) Value to be added.
:param key: Header parameters key.
:type key: str
:param value: Value to be added.
:type value: str
""" """
self.headers[key] = value self.headers[key] = value
def del_param_headers(self, key): def del_param_headers(self, key):
"""Remove a specific parameter. """Remove a specific parameter.
:param key: (str) Key of the header parameters.
:param key: Key of the header parameters.
:type key: str
""" """
self.headers.pop(key, None) self.headers.pop(key, None)
def raw_get(self, path, **kwargs): def raw_get(self, path, **kwargs):
"""Submit get request to the path. """Submit get request to the path.
:param path: (str) Path for request.
:param path: Path for request.
:type path: str
:param kwargs: Additional arguments
:type kwargs: dict
:returns: Response the request. :returns: Response the request.
:raises: HttpError Can't connect to server.
:rtype: Response
:raises KeycloakConnectionError: HttpError Can't connect to server.
""" """
try: try:
return self._s.get( return self._s.get(
@ -163,10 +207,15 @@ class ConnectionManager(object):
def raw_post(self, path, data, **kwargs): def raw_post(self, path, data, **kwargs):
"""Submit post request to the path. """Submit post request to the path.
:param path: (str) Path for request.
:param data: (dict) Payload for request.
:param path: Path for request.
:type path: str
:param data: Payload for request.
:type data: dict
:param kwargs: Additional arguments
:type kwargs: dict
:returns: Response the request. :returns: Response the request.
:raises: HttpError Can't connect to server.
:rtype: Response
:raises KeycloakConnectionError: HttpError Can't connect to server.
""" """
try: try:
return self._s.post( return self._s.post(
@ -183,10 +232,15 @@ class ConnectionManager(object):
def raw_put(self, path, data, **kwargs): def raw_put(self, path, data, **kwargs):
"""Submit put request to the path. """Submit put request to the path.
:param path: (str) Path for request.
:param data: (dict) Payload for request.
:param path: Path for request.
:type path: str
:param data: Payload for request.
:type data: dict
:param kwargs: Additional arguments
:type kwargs: dict
:returns: Response the request. :returns: Response the request.
:raises: HttpError Can't connect to server.
:rtype: Response
:raises KeycloakConnectionError: HttpError Can't connect to server.
""" """
try: try:
return self._s.put( return self._s.put(
@ -200,19 +254,24 @@ class ConnectionManager(object):
except Exception as e: except Exception as e:
raise KeycloakConnectionError("Can't connect to server (%s)" % e) raise KeycloakConnectionError("Can't connect to server (%s)" % e)
def raw_delete(self, path, data={}, **kwargs):
def raw_delete(self, path, data=None, **kwargs):
"""Submit delete request to the path. """Submit delete request to the path.
:param path: (str) Path for request.
:param data: (dict) Payload for request.
:param path: Path for request.
:type path: str
:param data: Payload for request.
:type data: dict | None
:param kwargs: Additional arguments
:type kwargs: dict
:returns: Response the request. :returns: Response the request.
:raises: HttpError Can't connect to server.
:rtype: Response
:raises KeycloakConnectionError: HttpError Can't connect to server.
""" """
try: try:
return self._s.delete( return self._s.delete(
urljoin(self.base_url, path), urljoin(self.base_url, path),
params=kwargs, params=kwargs,
data=data,
data=data or dict(),
headers=self.headers, headers=self.headers,
timeout=self.timeout, timeout=self.timeout,
verify=self.verify, verify=self.verify,

34
src/keycloak/exceptions.py

@ -21,7 +21,7 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""Keycloak custom exeptions module."""
"""Keycloak custom exceptions module."""
import requests import requests
@ -36,7 +36,15 @@ class KeycloakError(Exception):
""" """
def __init__(self, error_message="", response_code=None, response_body=None): def __init__(self, error_message="", response_code=None, response_body=None):
"""Init method."""
"""Init method.
:param error_message: The error message
:type error_message: str
:param response_code: The code of the response
:type response_code: int
:param response_body: Body of the response
:type response_body: bytes
"""
Exception.__init__(self, error_message) Exception.__init__(self, error_message)
self.response_code = response_code self.response_code = response_code
@ -44,7 +52,11 @@ class KeycloakError(Exception):
self.error_message = error_message self.error_message = error_message
def __str__(self): def __str__(self):
"""Str method."""
"""Str method.
:returns: String representation of the object
:rtype: str
"""
if self.response_code is not None: if self.response_code is not None:
return "{0}: {1}".format(self.response_code, self.error_message) return "{0}: {1}".format(self.response_code, self.error_message)
else: else:
@ -136,7 +148,21 @@ class PermissionDefinitionError(Exception):
def raise_error_from_response(response, error, expected_codes=None, skip_exists=False): def raise_error_from_response(response, error, expected_codes=None, skip_exists=False):
"""Raise an exception for the response."""
"""Raise an exception for the response.
:param response: The response object
:type response: Response
:param error: Error object to raise
:type error: dict or Exception
:param expected_codes: Set of expected codes, which should not raise the exception
:type expected_codes: Sequence[int]
:param skip_exists: Indicates whether the response on already existing object should be ignored
:type skip_exists: bool
:returns: Content of the response message
:type: bytes or dict
:raises KeycloakError: In case of unexpected status codes
""" # noqa: DAR401,DAR402
if expected_codes is None: if expected_codes is None:
expected_codes = [200, 201, 204] expected_codes = [200, 201, 204]

767
src/keycloak/keycloak_admin.py
File diff suppressed because it is too large
View File

220
src/keycloak/keycloak_openid.py

@ -81,7 +81,25 @@ class KeycloakOpenID:
proxies=None, proxies=None,
timeout=60, timeout=60,
): ):
"""Init method."""
"""Init method.
:param server_url: Keycloak server url
:type server_url: str
:param client_id: client id
:type client_id: str
:param realm_name: realm name
:type realm_name: str
:param client_secret_key: client secret key
:type client_secret_key: str
:param verify: True if want check connection SSL
:type verify: bool
:param custom_headers: dict of custom header to pass to each HTML request
:type custom_headers: dict
:param proxies: dict of proxies to sent the request by.
:type proxies: dict
:param timeout: connection timeout in seconds
:type timeout: int
"""
self.client_id = client_id self.client_id = client_id
self.client_secret_key = client_secret_key self.client_secret_key = client_secret_key
self.realm_name = realm_name self.realm_name = realm_name
@ -94,7 +112,11 @@ class KeycloakOpenID:
@property @property
def client_id(self): def client_id(self):
"""Get client id."""
"""Get client id.
:returns: Client id
:rtype: str
"""
return self._client_id return self._client_id
@client_id.setter @client_id.setter
@ -103,7 +125,11 @@ class KeycloakOpenID:
@property @property
def client_secret_key(self): def client_secret_key(self):
"""Get the client secret key."""
"""Get the client secret key.
:returns: Client secret key
:rtype: str
"""
return self._client_secret_key return self._client_secret_key
@client_secret_key.setter @client_secret_key.setter
@ -112,7 +138,11 @@ class KeycloakOpenID:
@property @property
def realm_name(self): def realm_name(self):
"""Get the realm name."""
"""Get the realm name.
:returns: Realm name
:rtype: str
"""
return self._realm_name return self._realm_name
@realm_name.setter @realm_name.setter
@ -121,7 +151,11 @@ class KeycloakOpenID:
@property @property
def connection(self): def connection(self):
"""Get connection."""
"""Get connection.
:returns: Connection manager object
:rtype: ConnectionManager
"""
return self._connection return self._connection
@connection.setter @connection.setter
@ -130,7 +164,11 @@ class KeycloakOpenID:
@property @property
def authorization(self): def authorization(self):
"""Get authorization."""
"""Get authorization.
:returns: The authorization manager
:rtype: Authorization
"""
return self._authorization return self._authorization
@authorization.setter @authorization.setter
@ -140,8 +178,10 @@ class KeycloakOpenID:
def _add_secret_key(self, payload): def _add_secret_key(self, payload):
"""Add secret key if exists. """Add secret key if exists.
:param payload:
:return:
:param payload: Payload
:type payload: dict
:returns: Payload with the secret key
:rtype: dict
""" """
if self.client_secret_key: if self.client_secret_key:
payload.update({"client_secret": self.client_secret_key}) payload.update({"client_secret": self.client_secret_key})
@ -151,18 +191,24 @@ class KeycloakOpenID:
def _build_name_role(self, role): def _build_name_role(self, role):
"""Build name of a role. """Build name of a role.
:param role:
:return:
:param role: Role name
:type role: str
:returns: Role path
:rtype: str
""" """
return self.client_id + "/" + role return self.client_id + "/" + role
def _token_info(self, token, method_token_info, **kwargs): def _token_info(self, token, method_token_info, **kwargs):
"""Getter for the token data. """Getter for the token data.
:param token:
:param method_token_info:
:param kwargs:
:return:
:param token: Token
:type token: str
:param method_token_info: Token info method to use
:type method_token_info: str
:param kwargs: Additional keyword arguments
:type kwargs: dict
:returns: Token info
:rtype: dict
""" """
if method_token_info == "introspect": if method_token_info == "introspect":
token_info = self.introspect(token) token_info = self.introspect(token)
@ -178,7 +224,8 @@ class KeycloakOpenID:
endpoint. It lists endpoints and other configuration options relevant to endpoint. It lists endpoints and other configuration options relevant to
the OpenID Connect implementation in Keycloak. the OpenID Connect implementation in Keycloak.
:return It lists endpoints and other configuration options relevant.
:returns: It lists endpoints and other configuration options relevant
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_get(URL_WELL_KNOWN.format(**params_path)) data_raw = self.connection.raw_get(URL_WELL_KNOWN.format(**params_path))
@ -190,9 +237,9 @@ class KeycloakOpenID:
:param redirect_uri: Redirect url to receive oauth code :param redirect_uri: Redirect url to receive oauth code
:type redirect_uri: str :type redirect_uri: str
:param scope: Scope of authorization request, split with the blank space :param scope: Scope of authorization request, split with the blank space
:type: scope: str
:type scope: str
:param state: State will be returned to the redirect_uri :param state: State will be returned to the redirect_uri
:type: str
:type state: str
:returns: Authorization URL Full Build :returns: Authorization URL Full Build
:rtype: str :rtype: str
""" """
@ -224,13 +271,22 @@ class KeycloakOpenID:
http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
:param username:
:param password:
:param grant_type:
:param code:
:param redirect_uri:
:param totp:
:return:
:param username: Username
:type username: str
:param password: Password
:type password: str
:param grant_type: Grant type
:type grant_type: str
:param code: Code
:type code: str
:param redirect_uri: Redirect URI
:type redirect_uri: str
:param totp: Time-based one-time password
:type totp: int
:param extra: Additional extra arguments
:type extra: dict
:returns: Keycloak token
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
payload = { payload = {
@ -261,9 +317,12 @@ class KeycloakOpenID:
http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
:param refresh_token:
:param grant_type:
:return:
:param refresh_token: Refresh token from Keycloak
:type refresh_token: str
:param grant_type: Grant type
:type grant_type: str
:returns: New token
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
payload = { payload = {
@ -289,13 +348,20 @@ class KeycloakOpenID:
Use a token to obtain an entirely different token. See Use a token to obtain an entirely different token. See
https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange
:param token:
:param client_id:
:param audience:
:param subject:
:param requested_token_type:
:param scope:
:return:
:param token: Access token
:type token: str
:param client_id: Client id
:type client_id: str
:param audience: Audience
:type audience: str
:param subject: Subject
:type subject: str
:param requested_token_type: Token type specification
:type requested_token_type: str
:param scope: Scope
:type scope: str
:returns: Exchanged token
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
payload = { payload = {
@ -319,8 +385,10 @@ class KeycloakOpenID:
http://openid.net/specs/openid-connect-core-1_0.html#UserInfo http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
:param token:
:return:
:param token: Access token
:type token: str
:returns: Userinfo object
:rtype: dict
""" """
self.connection.add_param_headers("Authorization", "Bearer " + token) self.connection.add_param_headers("Authorization", "Bearer " + token)
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
@ -330,8 +398,10 @@ class KeycloakOpenID:
def logout(self, refresh_token): def logout(self, refresh_token):
"""Log out the authenticated user. """Log out the authenticated user.
:param refresh_token:
:return:
:param refresh_token: Refresh token from Keycloak
:type refresh_token: str
:returns: Keycloak server response
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
payload = {"client_id": self.client_id, "refresh_token": refresh_token} payload = {"client_id": self.client_id, "refresh_token": refresh_token}
@ -348,7 +418,8 @@ class KeycloakOpenID:
https://tools.ietf.org/html/rfc7517 https://tools.ietf.org/html/rfc7517
:return:
:returns: Certificates
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_get(URL_CERTS.format(**params_path)) data_raw = self.connection.raw_get(URL_CERTS.format(**params_path))
@ -359,7 +430,8 @@ class KeycloakOpenID:
The public key is exposed by the realm page directly. The public key is exposed by the realm page directly.
:return:
:returns: The public key
:rtype: str
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_get(URL_REALM.format(**params_path)) data_raw = self.connection.raw_get(URL_REALM.format(**params_path))
@ -374,7 +446,12 @@ class KeycloakOpenID:
authorization policies associated with the resources being requested. With an RPT, authorization policies associated with the resources being requested. With an RPT,
client applications can gain access to protected resources at the resource server. client applications can gain access to protected resources at the resource server.
:return:
:param token: Access token
:type token: str
:param resource_server_id: Resource server ID
:type resource_server_id: str
:returns: Entitlements
:rtype: dict
""" """
self.connection.add_param_headers("Authorization", "Bearer " + token) self.connection.add_param_headers("Authorization", "Bearer " + token)
params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id} params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id}
@ -393,11 +470,16 @@ class KeycloakOpenID:
https://tools.ietf.org/html/rfc7662 https://tools.ietf.org/html/rfc7662
:param token:
:param rpt:
:param token_type_hint:
:param token: Access token
:type token: str
:param rpt: Requesting party token
:type rpt: str
:param token_type_hint: Token type hint
:type token_type_hint: str
:return:
:returns: Token info
:rtype: dict
:raises KeycloakRPTNotFound: In case of RPT not specified
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
payload = {"client_id": self.client_id, "token": token} payload = {"client_id": self.client_id, "token": token}
@ -426,10 +508,16 @@ class KeycloakOpenID:
https://tools.ietf.org/html/rfc7517 https://tools.ietf.org/html/rfc7517
:param token:
:param key:
:param algorithms:
:return:
:param token: Keycloak token
:type token: str
:param key: Decode key
:type key: str
:param algorithms: Algorithms to use for decoding
:type algorithms: list[str]
:param kwargs: Keyword arguments
:type kwargs: dict
:returns: Decoded token
:rtype: dict
""" """
return jwt.decode(token, key, algorithms=algorithms, audience=self.client_id, **kwargs) return jwt.decode(token, key, algorithms=algorithms, audience=self.client_id, **kwargs)
@ -437,7 +525,7 @@ class KeycloakOpenID:
"""Load Keycloak settings (authorization). """Load Keycloak settings (authorization).
:param path: settings file (json) :param path: settings file (json)
:return:
:type path: str
""" """
with open(path, "r") as fp: with open(path, "r") as fp:
authorization_json = json.load(fp) authorization_json = json.load(fp)
@ -447,8 +535,16 @@ class KeycloakOpenID:
def get_policies(self, token, method_token_info="introspect", **kwargs): def get_policies(self, token, method_token_info="introspect", **kwargs):
"""Get policies by user token. """Get policies by user token.
:param token: user token
:return: policies list
:param token: User token
:type token: str
:param method_token_info: Method for token info decoding
:type method_token_info: str
:param kwargs: Additional keyword arguments
:type kwargs: dict
:return: Policies
:rtype: dict
:raises KeycloakAuthorizationConfigError: In case of bad authorization configuration
:raises KeycloakInvalidTokenError: In case of bad token
""" """
if not self.authorization.policies: if not self.authorization.policies:
raise KeycloakAuthorizationConfigError( raise KeycloakAuthorizationConfigError(
@ -478,9 +574,15 @@ class KeycloakOpenID:
"""Get permission by user token. """Get permission by user token.
:param token: user token :param token: user token
:type token: str
:param method_token_info: Decode token method :param method_token_info: Decode token method
:type method_token_info: str
:param kwargs: parameters for decode :param kwargs: parameters for decode
:return: permissions list
:type kwargs: dict
:returns: permissions list
:rtype: list
:raises KeycloakAuthorizationConfigError: In case of bad authorization configuration
:raises KeycloakInvalidTokenError: In case of bad token
""" """
if not self.authorization.policies: if not self.authorization.policies:
raise KeycloakAuthorizationConfigError( raise KeycloakAuthorizationConfigError(
@ -515,8 +617,11 @@ class KeycloakOpenID:
http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
:param token: user token :param token: user token
:type token: str
:param permissions: list of uma permissions list(resource:scope) requested by the user :param permissions: list of uma permissions list(resource:scope) requested by the user
:return: permissions list
:type permissions: str
:returns: Keycloak server response
:rtype: dict
""" """
permission = build_permission_param(permissions) permission = build_permission_param(permissions)
@ -536,8 +641,13 @@ class KeycloakOpenID:
"""Determine whether user has uma permissions with specified user token. """Determine whether user has uma permissions with specified user token.
:param token: user token :param token: user token
:type token: str
:param permissions: list of uma permissions (resource:scope) :param permissions: list of uma permissions (resource:scope)
:return: auth status
:type permissions: str
:return: Authentication status
:rtype: AuthStatus
:raises KeycloakAuthenticationError: In case of failed authentication
:raises KeycloakPostError: In case of failed request to Keycloak
""" """
needed = build_permission_param(permissions) needed = build_permission_param(permissions)
try: try:

96
src/keycloak/uma_permissions.py

@ -48,7 +48,16 @@ class UMAPermission:
""" """
def __init__(self, permission=None, resource="", scope=""): def __init__(self, permission=None, resource="", scope=""):
"""Init method."""
"""Init method.
:param permission: Permission
:type permission: UMAPermission
:param resource: Resource
:type resource: str
:param scope: Scope
:type scope: str
:raises PermissionDefinitionError: In case bad permission definition
"""
self.resource = resource self.resource = resource
self.scope = scope self.scope = scope
@ -63,26 +72,55 @@ class UMAPermission:
self.scope = str(permission.scope) self.scope = str(permission.scope)
def __str__(self): def __str__(self):
"""Str method."""
"""Str method.
:returns: String representation
:rtype: str
"""
scope = self.scope scope = self.scope
if scope: if scope:
scope = "#" + scope scope = "#" + scope
return "{}{}".format(self.resource, scope) return "{}{}".format(self.resource, scope)
def __eq__(self, __o: object) -> bool: def __eq__(self, __o: object) -> bool:
"""Eq method."""
"""Eq method.
:param __o: The other object
:type __o: object
:returns: Equality boolean
:rtype: bool
"""
return str(self) == str(__o) return str(self) == str(__o)
def __repr__(self) -> str: def __repr__(self) -> str:
"""Repr method."""
"""Repr method.
:returns: The object representation
:rtype: str
"""
return self.__str__() return self.__str__()
def __hash__(self) -> int: def __hash__(self) -> int:
"""Hash method."""
"""Hash method.
:returns: Hash of the object
:rtype: int
"""
return hash(str(self)) return hash(str(self))
def __call__(self, permission=None, resource="", scope="") -> object:
"""Call method."""
def __call__(self, permission=None, resource="", scope="") -> "UMAPermission":
"""Call method.
:param permission: Permission
:type permission: UMAPermission
:param resource: Resource
:type resource: str
:param scope: Scope
:type scope: str
:returns: The combined UMA permission
:rtype: UMAPermission
:raises PermissionDefinitionError: In case bad permission definition
"""
result_resource = self.resource result_resource = self.resource
result_scope = self.scope result_scope = self.scope
@ -114,7 +152,11 @@ class Resource(UMAPermission):
""" """
def __init__(self, resource): def __init__(self, resource):
"""Init method."""
"""Init method.
:param resource: Resource
:type resource: str
"""
super().__init__(resource=resource) super().__init__(resource=resource)
@ -128,7 +170,11 @@ class Scope(UMAPermission):
""" """
def __init__(self, scope): def __init__(self, scope):
"""Init method."""
"""Init method.
:param scope: Scope
:type scope: str
"""
super().__init__(scope=scope) super().__init__(scope=scope)
@ -147,17 +193,33 @@ class AuthStatus:
""" """
def __init__(self, is_logged_in, is_authorized, missing_permissions): def __init__(self, is_logged_in, is_authorized, missing_permissions):
"""Init method."""
"""Init method.
:param is_logged_in: Is logged in indicator
:type is_logged_in: bool
:param is_authorized: Is authorized indicator
:type is_authorized: bool
:param missing_permissions: Missing permissions
:type missing_permissions: set
"""
self.is_logged_in = is_logged_in self.is_logged_in = is_logged_in
self.is_authorized = is_authorized self.is_authorized = is_authorized
self.missing_permissions = missing_permissions self.missing_permissions = missing_permissions
def __bool__(self): def __bool__(self):
"""Bool method."""
"""Bool method.
:returns: Boolean representation
:rtype: bool
"""
return self.is_authorized return self.is_authorized
def __repr__(self): def __repr__(self):
"""Repr method."""
"""Repr method.
:returns: The object representation
:rtype: str
"""
return ( return (
f"AuthStatus(" f"AuthStatus("
f"is_authorized={self.is_authorized}, " f"is_authorized={self.is_authorized}, "
@ -169,11 +231,11 @@ class AuthStatus:
def build_permission_param(permissions): def build_permission_param(permissions):
"""Transform permissions to a set, so they are usable for requests. """Transform permissions to a set, so they are usable for requests.
:param permissions: either str (resource#scope),
iterable[str] (resource#scope),
dict[str,str] (resource: scope),
dict[str,iterable[str]] (resource: scopes)
:return: result bool
:param permissions: Permissions
:type permissions: str | Iterable[str] | dict[str, str] | dict[str, Iterabble[str]]
:returns: Permission parameters
:rtype: set
:raises KeycloakPermissionFormatError: In case of bad permission format
""" """
if permissions is None or permissions == "": if permissions is None or permissions == "":
return set() return set()

180
tests/conftest.py

@ -35,7 +35,17 @@ class KeycloakTestEnv(object):
username: str = os.environ["KEYCLOAK_ADMIN"], username: str = os.environ["KEYCLOAK_ADMIN"],
password: str = os.environ["KEYCLOAK_ADMIN_PASSWORD"], password: str = os.environ["KEYCLOAK_ADMIN_PASSWORD"],
): ):
"""Init method."""
"""Init method.
:param host: Hostname
:type host: str
:param port: Port
:type port: str
:param username: Admin username
:type username: str
:param password: Admin password
:type password: str
"""
self.KEYCLOAK_HOST = host self.KEYCLOAK_HOST = host
self.KEYCLOAK_PORT = port self.KEYCLOAK_PORT = port
self.KEYCLOAK_ADMIN = username self.KEYCLOAK_ADMIN = username
@ -43,54 +53,96 @@ class KeycloakTestEnv(object):
@property @property
def KEYCLOAK_HOST(self): def KEYCLOAK_HOST(self):
"""Hostname getter."""
"""Hostname getter.
:returns: Keycloak host
:rtype: str
"""
return self._KEYCLOAK_HOST return self._KEYCLOAK_HOST
@KEYCLOAK_HOST.setter @KEYCLOAK_HOST.setter
def KEYCLOAK_HOST(self, value: str): def KEYCLOAK_HOST(self, value: str):
"""Hostname setter."""
"""Hostname setter.
:param value: Keycloak host
:type value: str
"""
self._KEYCLOAK_HOST = value self._KEYCLOAK_HOST = value
@property @property
def KEYCLOAK_PORT(self): def KEYCLOAK_PORT(self):
"""Port getter."""
"""Port getter.
:returns: Keycloak port
:rtype: str
"""
return self._KEYCLOAK_PORT return self._KEYCLOAK_PORT
@KEYCLOAK_PORT.setter @KEYCLOAK_PORT.setter
def KEYCLOAK_PORT(self, value: str): def KEYCLOAK_PORT(self, value: str):
"""Port setter."""
"""Port setter.
:param value: Keycloak port
:type value: str
"""
self._KEYCLOAK_PORT = value self._KEYCLOAK_PORT = value
@property @property
def KEYCLOAK_ADMIN(self): def KEYCLOAK_ADMIN(self):
"""Admin username getter."""
"""Admin username getter.
:returns: Admin username
:rtype: str
"""
return self._KEYCLOAK_ADMIN return self._KEYCLOAK_ADMIN
@KEYCLOAK_ADMIN.setter @KEYCLOAK_ADMIN.setter
def KEYCLOAK_ADMIN(self, value: str): def KEYCLOAK_ADMIN(self, value: str):
"""Admin username setter."""
"""Admin username setter.
:param value: Admin username
:type value: str
"""
self._KEYCLOAK_ADMIN = value self._KEYCLOAK_ADMIN = value
@property @property
def KEYCLOAK_ADMIN_PASSWORD(self): def KEYCLOAK_ADMIN_PASSWORD(self):
"""Admin password getter."""
"""Admin password getter.
:returns: Admin password
:rtype: str
"""
return self._KEYCLOAK_ADMIN_PASSWORD return self._KEYCLOAK_ADMIN_PASSWORD
@KEYCLOAK_ADMIN_PASSWORD.setter @KEYCLOAK_ADMIN_PASSWORD.setter
def KEYCLOAK_ADMIN_PASSWORD(self, value: str): def KEYCLOAK_ADMIN_PASSWORD(self, value: str):
"""Admin password setter."""
"""Admin password setter.
:param value: Admin password
:type value: str
"""
self._KEYCLOAK_ADMIN_PASSWORD = value self._KEYCLOAK_ADMIN_PASSWORD = value
@pytest.fixture @pytest.fixture
def env(): def env():
"""Fixture for getting the test environment configuration object."""
"""Fixture for getting the test environment configuration object.
:returns: Keycloak test environment object
:rtype: KeycloakTestEnv
"""
return KeycloakTestEnv() return KeycloakTestEnv()
@pytest.fixture @pytest.fixture
def admin(env: KeycloakTestEnv): def admin(env: KeycloakTestEnv):
"""Fixture for initialized KeycloakAdmin class."""
"""Fixture for initialized KeycloakAdmin class.
:param env: Keycloak test environment
:type env: KeycloakTestEnv
:returns: Keycloak admin
:rtype: KeycloakAdmin
"""
return KeycloakAdmin( return KeycloakAdmin(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
username=env.KEYCLOAK_ADMIN, username=env.KEYCLOAK_ADMIN,
@ -100,7 +152,17 @@ def admin(env: KeycloakTestEnv):
@pytest.fixture @pytest.fixture
def oid(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin): def oid(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
"""Fixture for initialized KeycloakOpenID class."""
"""Fixture for initialized KeycloakOpenID class.
:param env: Keycloak test environment
:type env: KeycloakTestEnv
:param realm: Keycloak realm
:type realm: str
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:yields: Keycloak OpenID client
:rtype: KeycloakOpenID
"""
# Set the realm # Set the realm
admin.realm_name = realm admin.realm_name = realm
# Create client # Create client
@ -126,7 +188,17 @@ def oid(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
@pytest.fixture @pytest.fixture
def oid_with_credentials(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin): def oid_with_credentials(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
"""Fixture for an initialized KeycloakOpenID class and a random user credentials."""
"""Fixture for an initialized KeycloakOpenID class and a random user credentials.
:param env: Keycloak test environment
:type env: KeycloakTestEnv
:param realm: Keycloak realm
:type realm: str
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:yields: Keycloak OpenID client with user credentials
:rtype: Tuple[KeycloakOpenID, str, str]
"""
# Set the realm # Set the realm
admin.realm_name = realm admin.realm_name = realm
# Create client # Create client
@ -173,7 +245,17 @@ def oid_with_credentials(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin)
@pytest.fixture @pytest.fixture
def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin): def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
"""Fixture for an initialized KeycloakOpenID class and a random user credentials."""
"""Fixture for an initialized KeycloakOpenID class and a random user credentials.
:param env: Keycloak test environment
:type env: KeycloakTestEnv
:param realm: Keycloak realm
:type realm: str
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:yields: Keycloak OpenID client configured as an authorization server with client credentials
:rtype: Tuple[KeycloakOpenID, str, str]
"""
# Set the realm # Set the realm
admin.realm_name = realm admin.realm_name = realm
# Create client # Create client
@ -229,7 +311,13 @@ def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: Keycloak
@pytest.fixture @pytest.fixture
def realm(admin: KeycloakAdmin) -> str: def realm(admin: KeycloakAdmin) -> str:
"""Fixture for a new random realm."""
"""Fixture for a new random realm.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:yields: Keycloak realm
:rtype: str
"""
realm_name = str(uuid.uuid4()) realm_name = str(uuid.uuid4())
admin.create_realm(payload={"realm": realm_name, "enabled": True}) admin.create_realm(payload={"realm": realm_name, "enabled": True})
yield realm_name yield realm_name
@ -238,7 +326,15 @@ def realm(admin: KeycloakAdmin) -> str:
@pytest.fixture @pytest.fixture
def user(admin: KeycloakAdmin, realm: str) -> str: def user(admin: KeycloakAdmin, realm: str) -> str:
"""Fixture for a new random user."""
"""Fixture for a new random user.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:yields: Keycloak user
:rtype: str
"""
admin.realm_name = realm admin.realm_name = realm
username = str(uuid.uuid4()) username = str(uuid.uuid4())
user_id = admin.create_user(payload={"username": username, "email": f"{username}@test.test"}) user_id = admin.create_user(payload={"username": username, "email": f"{username}@test.test"})
@ -248,7 +344,15 @@ def user(admin: KeycloakAdmin, realm: str) -> str:
@pytest.fixture @pytest.fixture
def group(admin: KeycloakAdmin, realm: str) -> str: def group(admin: KeycloakAdmin, realm: str) -> str:
"""Fixture for a new random group."""
"""Fixture for a new random group.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:yields: Keycloak group
:rtype: str
"""
admin.realm_name = realm admin.realm_name = realm
group_name = str(uuid.uuid4()) group_name = str(uuid.uuid4())
group_id = admin.create_group(payload={"name": group_name}) group_id = admin.create_group(payload={"name": group_name})
@ -258,7 +362,15 @@ def group(admin: KeycloakAdmin, realm: str) -> str:
@pytest.fixture @pytest.fixture
def client(admin: KeycloakAdmin, realm: str) -> str: def client(admin: KeycloakAdmin, realm: str) -> str:
"""Fixture for a new random client."""
"""Fixture for a new random client.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:yields: Keycloak client id
:rtype: str
"""
admin.realm_name = realm admin.realm_name = realm
client = str(uuid.uuid4()) client = str(uuid.uuid4())
client_id = admin.create_client(payload={"name": client, "clientId": client}) client_id = admin.create_client(payload={"name": client, "clientId": client})
@ -268,7 +380,17 @@ def client(admin: KeycloakAdmin, realm: str) -> str:
@pytest.fixture @pytest.fixture
def client_role(admin: KeycloakAdmin, realm: str, client: str) -> str: def client_role(admin: KeycloakAdmin, realm: str, client: str) -> str:
"""Fixture for a new random client role."""
"""Fixture for a new random client role.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:yields: Keycloak client role
:rtype: str
"""
admin.realm_name = realm admin.realm_name = realm
role = str(uuid.uuid4()) role = str(uuid.uuid4())
admin.create_client_role(client, {"name": role, "composite": False}) admin.create_client_role(client, {"name": role, "composite": False})
@ -278,7 +400,19 @@ def client_role(admin: KeycloakAdmin, realm: str, client: str) -> str:
@pytest.fixture @pytest.fixture
def composite_client_role(admin: KeycloakAdmin, realm: str, client: str, client_role: str) -> str: def composite_client_role(admin: KeycloakAdmin, realm: str, client: str, client_role: str) -> str:
"""Fixture for a new random composite client role."""
"""Fixture for a new random composite client role.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param client_role: Keycloak client role
:type client_role: str
:yields: Composite client role
:rtype: str
"""
admin.realm_name = realm admin.realm_name = realm
role = str(uuid.uuid4()) role = str(uuid.uuid4())
admin.create_client_role(client, {"name": role, "composite": True}) admin.create_client_role(client, {"name": role, "composite": True})
@ -290,7 +424,11 @@ def composite_client_role(admin: KeycloakAdmin, realm: str, client: str, client_
@pytest.fixture @pytest.fixture
def selfsigned_cert(): def selfsigned_cert():
"""Generate self signed certificate for a hostname, and optional IP addresses."""
"""Generate self signed certificate for a hostname, and optional IP addresses.
:returns: Selfsigned certificate
:rtype: Tuple[str, str]
"""
hostname = "testcert" hostname = "testcert"
ip_addresses = None ip_addresses = None
key = None key = None

297
tests/test_keycloak_admin.py

@ -22,7 +22,11 @@ def test_keycloak_version():
def test_keycloak_admin_bad_init(env): def test_keycloak_admin_bad_init(env):
"""Test keycloak admin bad init."""
"""Test keycloak admin bad init.
:param env: Environment fixture
:type env: KeycloakTestEnv
"""
with pytest.raises(TypeError) as err: with pytest.raises(TypeError) as err:
KeycloakAdmin( KeycloakAdmin(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
@ -43,7 +47,11 @@ def test_keycloak_admin_bad_init(env):
def test_keycloak_admin_init(env): def test_keycloak_admin_init(env):
"""Test keycloak admin init."""
"""Test keycloak admin init.
:param env: Environment fixture
:type env: KeycloakTestEnv
"""
admin = KeycloakAdmin( admin = KeycloakAdmin(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
username=env.KEYCLOAK_ADMIN, username=env.KEYCLOAK_ADMIN,
@ -118,7 +126,11 @@ def test_keycloak_admin_init(env):
def test_realms(admin: KeycloakAdmin): def test_realms(admin: KeycloakAdmin):
"""Test realms."""
"""Test realms.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
# Get realms # Get realms
realms = admin.get_realms() realms = admin.get_realms()
assert len(realms) == 1, realms assert len(realms) == 1, realms
@ -183,7 +195,13 @@ def test_realms(admin: KeycloakAdmin):
def test_import_export_realms(admin: KeycloakAdmin, realm: str): def test_import_export_realms(admin: KeycloakAdmin, realm: str):
"""Test import and export of realms."""
"""Test import and export of realms.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
realm_export = admin.export_realm(export_clients=True, export_groups_and_role=True) realm_export = admin.export_realm(export_clients=True, export_groups_and_role=True)
@ -201,7 +219,13 @@ def test_import_export_realms(admin: KeycloakAdmin, realm: str):
def test_users(admin: KeycloakAdmin, realm: str): def test_users(admin: KeycloakAdmin, realm: str):
"""Test users."""
"""Test users.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Check no users present # Check no users present
@ -293,7 +317,13 @@ def test_users(admin: KeycloakAdmin, realm: str):
def test_users_pagination(admin: KeycloakAdmin, realm: str): def test_users_pagination(admin: KeycloakAdmin, realm: str):
"""Test user pagination."""
"""Test user pagination.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
for ind in range(admin.PAGE_SIZE + 50): for ind in range(admin.PAGE_SIZE + 50):
@ -311,7 +341,13 @@ def test_users_pagination(admin: KeycloakAdmin, realm: str):
def test_idps(admin: KeycloakAdmin, realm: str): def test_idps(admin: KeycloakAdmin, realm: str):
"""Test IDPs."""
"""Test IDPs.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Create IDP # Create IDP
@ -383,7 +419,13 @@ def test_idps(admin: KeycloakAdmin, realm: str):
def test_user_credentials(admin: KeycloakAdmin, user: str): def test_user_credentials(admin: KeycloakAdmin, user: str):
"""Test user credentials."""
"""Test user credentials.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param user: Keycloak user
:type user: str
"""
res = admin.set_user_password(user_id=user, password="booya", temporary=True) res = admin.set_user_password(user_id=user, password="booya", temporary=True)
assert res == dict(), res assert res == dict(), res
@ -411,7 +453,13 @@ def test_user_credentials(admin: KeycloakAdmin, user: str):
def test_social_logins(admin: KeycloakAdmin, user: str): def test_social_logins(admin: KeycloakAdmin, user: str):
"""Test social logins."""
"""Test social logins.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param user: Keycloak user
:type user: str
"""
res = admin.add_user_social_login( res = admin.add_user_social_login(
user_id=user, provider_id="gitlab", provider_userid="test", provider_username="test" user_id=user, provider_id="gitlab", provider_userid="test", provider_username="test"
) )
@ -451,7 +499,11 @@ def test_social_logins(admin: KeycloakAdmin, user: str):
def test_server_info(admin: KeycloakAdmin): def test_server_info(admin: KeycloakAdmin):
"""Test server info."""
"""Test server info.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
info = admin.get_server_info() info = admin.get_server_info()
assert set(info.keys()) == { assert set(info.keys()) == {
"systemInfo", "systemInfo",
@ -471,7 +523,13 @@ def test_server_info(admin: KeycloakAdmin):
def test_groups(admin: KeycloakAdmin, user: str): def test_groups(admin: KeycloakAdmin, user: str):
"""Test groups."""
"""Test groups.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param user: Keycloak user
:type user: str
"""
# Test get groups # Test get groups
groups = admin.get_groups() groups = admin.get_groups()
assert len(groups) == 0 assert len(groups) == 0
@ -615,7 +673,13 @@ def test_groups(admin: KeycloakAdmin, user: str):
def test_clients(admin: KeycloakAdmin, realm: str): def test_clients(admin: KeycloakAdmin, realm: str):
"""Test clients."""
"""Test clients.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Test get clients # Test get clients
@ -875,7 +939,13 @@ def test_clients(admin: KeycloakAdmin, realm: str):
def test_realm_roles(admin: KeycloakAdmin, realm: str): def test_realm_roles(admin: KeycloakAdmin, realm: str):
"""Test realm roles."""
"""Test realm roles.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Test get realm roles # Test get realm roles
@ -1046,7 +1116,21 @@ def test_role_attributes(
includes_attributes: bool, includes_attributes: bool,
testcase: str, testcase: str,
): ):
"""Test getting role attributes for bulk calls."""
"""Test getting role attributes for bulk calls.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param arg_brief_repr: Brief representation
:type arg_brief_repr: dict
:param includes_attributes: Indicator whether to include attributes
:type includes_attributes: bool
:param testcase: Test case
:type testcase: str
"""
# setup # setup
attribute_role = "test-realm-role-w-attr" attribute_role = "test-realm-role-w-attr"
test_attrs = {"attr1": ["val1"], "attr2": ["val2-1", "val2-2"]} test_attrs = {"attr1": ["val1"], "attr2": ["val2-1", "val2-2"]}
@ -1088,7 +1172,13 @@ def test_role_attributes(
def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str):
"""Test client realm roles."""
"""Test client realm roles.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Test get realm roles # Test get realm roles
@ -1146,7 +1236,15 @@ def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str):
def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str): def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str):
"""Test client assignment of other client roles."""
"""Test client assignment of other client roles.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
"""
admin.realm_name = realm admin.realm_name = realm
client_id = admin.create_client( client_id = admin.create_client(
@ -1202,7 +1300,13 @@ def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str
def test_client_roles(admin: KeycloakAdmin, client: str): def test_client_roles(admin: KeycloakAdmin, client: str):
"""Test client roles."""
"""Test client roles.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param client: Keycloak client
:type client: str
"""
# Test get client roles # Test get client roles
res = admin.get_client_roles(client_id=client) res = admin.get_client_roles(client_id=client)
assert len(res) == 0 assert len(res) == 0
@ -1365,7 +1469,14 @@ def test_client_roles(admin: KeycloakAdmin, client: str):
def test_enable_token_exchange(admin: KeycloakAdmin, realm: str): def test_enable_token_exchange(admin: KeycloakAdmin, realm: str):
"""Test enable token exchange."""
"""Test enable token exchange.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:raises AssertionError: In case of bad configuration
"""
# Test enabling token exchange between two confidential clients # Test enabling token exchange between two confidential clients
admin.realm_name = realm admin.realm_name = realm
@ -1454,7 +1565,13 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str):
def test_email(admin: KeycloakAdmin, user: str): def test_email(admin: KeycloakAdmin, user: str):
"""Test email."""
"""Test email.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param user: Keycloak user
:type user: str
"""
# Emails will fail as we don't have SMTP test setup # Emails will fail as we don't have SMTP test setup
with pytest.raises(KeycloakPutError) as err: with pytest.raises(KeycloakPutError) as err:
admin.send_update_account(user_id=user, payload=dict()) admin.send_update_account(user_id=user, payload=dict())
@ -1467,7 +1584,11 @@ def test_email(admin: KeycloakAdmin, user: str):
def test_get_sessions(admin: KeycloakAdmin): def test_get_sessions(admin: KeycloakAdmin):
"""Test get sessions."""
"""Test get sessions.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
sessions = admin.get_sessions(user_id=admin.get_user_id(username=admin.username)) sessions = admin.get_sessions(user_id=admin.get_user_id(username=admin.username))
assert len(sessions) >= 1 assert len(sessions) >= 1
with pytest.raises(KeycloakGetError) as err: with pytest.raises(KeycloakGetError) as err:
@ -1476,7 +1597,13 @@ def test_get_sessions(admin: KeycloakAdmin):
def test_get_client_installation_provider(admin: KeycloakAdmin, client: str): def test_get_client_installation_provider(admin: KeycloakAdmin, client: str):
"""Test get client installation provider."""
"""Test get client installation provider.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param client: Keycloak client
:type client: str
"""
with pytest.raises(KeycloakGetError) as err: with pytest.raises(KeycloakGetError) as err:
admin.get_client_installation_provider(client_id=client, provider_id="bad") admin.get_client_installation_provider(client_id=client, provider_id="bad")
assert err.match('404: b\'{"error":"Unknown Provider"}\'') assert err.match('404: b\'{"error":"Unknown Provider"}\'')
@ -1495,7 +1622,13 @@ def test_get_client_installation_provider(admin: KeycloakAdmin, client: str):
def test_auth_flows(admin: KeycloakAdmin, realm: str): def test_auth_flows(admin: KeycloakAdmin, realm: str):
"""Test auth flows."""
"""Test auth flows.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
res = admin.get_authentication_flows() res = admin.get_authentication_flows()
@ -1642,7 +1775,13 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str):
def test_authentication_configs(admin: KeycloakAdmin, realm: str): def test_authentication_configs(admin: KeycloakAdmin, realm: str):
"""Test authentication configs."""
"""Test authentication configs.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Test list of auth providers # Test list of auth providers
@ -1674,7 +1813,13 @@ def test_authentication_configs(admin: KeycloakAdmin, realm: str):
def test_sync_users(admin: KeycloakAdmin, realm: str): def test_sync_users(admin: KeycloakAdmin, realm: str):
"""Test sync users."""
"""Test sync users.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Only testing the error message # Only testing the error message
@ -1684,7 +1829,13 @@ def test_sync_users(admin: KeycloakAdmin, realm: str):
def test_client_scopes(admin: KeycloakAdmin, realm: str): def test_client_scopes(admin: KeycloakAdmin, realm: str):
"""Test client scopes."""
"""Test client scopes.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Test get client scopes # Test get client scopes
@ -1822,7 +1973,13 @@ def test_client_scopes(admin: KeycloakAdmin, realm: str):
def test_components(admin: KeycloakAdmin, realm: str): def test_components(admin: KeycloakAdmin, realm: str):
"""Test components."""
"""Test components.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
# Test get components # Test get components
@ -1873,7 +2030,13 @@ def test_components(admin: KeycloakAdmin, realm: str):
def test_keys(admin: KeycloakAdmin, realm: str): def test_keys(admin: KeycloakAdmin, realm: str):
"""Test keys."""
"""Test keys.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
assert set(admin.get_keys()["active"].keys()) == {"AES", "HS256", "RS256", "RSA-OAEP"} assert set(admin.get_keys()["active"].keys()) == {"AES", "HS256", "RS256", "RSA-OAEP"}
assert {k["algorithm"] for k in admin.get_keys()["keys"]} == { assert {k["algorithm"] for k in admin.get_keys()["keys"]} == {
@ -1885,7 +2048,13 @@ def test_keys(admin: KeycloakAdmin, realm: str):
def test_events(admin: KeycloakAdmin, realm: str): def test_events(admin: KeycloakAdmin, realm: str):
"""Test events."""
"""Test events.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
events = admin.get_events() events = admin.get_events()
@ -1905,7 +2074,13 @@ def test_events(admin: KeycloakAdmin, realm: str):
def test_auto_refresh(admin: KeycloakAdmin, realm: str): def test_auto_refresh(admin: KeycloakAdmin, realm: str):
"""Test auto refresh token."""
"""Test auto refresh token.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
# Test get refresh # Test get refresh
admin.auto_refresh_token = list() admin.auto_refresh_token = list()
admin.connection = ConnectionManager( admin.connection = ConnectionManager(
@ -1989,7 +2164,13 @@ def test_auto_refresh(admin: KeycloakAdmin, realm: str):
def test_get_required_actions(admin: KeycloakAdmin, realm: str): def test_get_required_actions(admin: KeycloakAdmin, realm: str):
"""Test requried actions."""
"""Test required actions.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
ractions = admin.get_required_actions() ractions = admin.get_required_actions()
assert isinstance(ractions, list) assert isinstance(ractions, list)
@ -2007,7 +2188,13 @@ def test_get_required_actions(admin: KeycloakAdmin, realm: str):
def test_get_required_action_by_alias(admin: KeycloakAdmin, realm: str): def test_get_required_action_by_alias(admin: KeycloakAdmin, realm: str):
"""Test get required action by alias."""
"""Test get required action by alias.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
ractions = admin.get_required_actions() ractions = admin.get_required_actions()
ra = admin.get_required_action_by_alias("UPDATE_PASSWORD") ra = admin.get_required_action_by_alias("UPDATE_PASSWORD")
@ -2017,7 +2204,13 @@ def test_get_required_action_by_alias(admin: KeycloakAdmin, realm: str):
def test_update_required_action(admin: KeycloakAdmin, realm: str): def test_update_required_action(admin: KeycloakAdmin, realm: str):
"""Test update required action."""
"""Test update required action.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm admin.realm_name = realm
ra = admin.get_required_action_by_alias("UPDATE_PASSWORD") ra = admin.get_required_action_by_alias("UPDATE_PASSWORD")
old = copy.deepcopy(ra) old = copy.deepcopy(ra)
@ -2031,7 +2224,19 @@ def test_update_required_action(admin: KeycloakAdmin, realm: str):
def test_get_composite_client_roles_of_group( def test_get_composite_client_roles_of_group(
admin: KeycloakAdmin, realm: str, client: str, group: str, composite_client_role: str admin: KeycloakAdmin, realm: str, client: str, group: str, composite_client_role: str
): ):
"""Test get composite client roles of group."""
"""Test get composite client roles of group.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param group: Keycloak group
:type group: str
:param composite_client_role: Composite client role
:type composite_client_role: str
"""
admin.realm_name = realm admin.realm_name = realm
role = admin.get_client_role(client, composite_client_role) role = admin.get_client_role(client, composite_client_role)
admin.assign_group_client_roles(group_id=group, client_id=client, roles=[role]) admin.assign_group_client_roles(group_id=group, client_id=client, roles=[role])
@ -2042,7 +2247,19 @@ def test_get_composite_client_roles_of_group(
def test_get_role_client_level_children( def test_get_role_client_level_children(
admin: KeycloakAdmin, realm: str, client: str, composite_client_role: str, client_role: str admin: KeycloakAdmin, realm: str, client: str, composite_client_role: str, client_role: str
): ):
"""Test get children of composite client role."""
"""Test get children of composite client role.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param composite_client_role: Composite client role
:type composite_client_role: str
:param client_role: Client role
:type client_role: str
"""
admin.realm_name = realm admin.realm_name = realm
child = admin.get_client_role(client, client_role) child = admin.get_client_role(client, client_role)
parent = admin.get_client_role(client, composite_client_role) parent = admin.get_client_role(client, composite_client_role)
@ -2051,7 +2268,17 @@ def test_get_role_client_level_children(
def test_upload_certificate(admin: KeycloakAdmin, realm: str, client: str, selfsigned_cert: tuple): def test_upload_certificate(admin: KeycloakAdmin, realm: str, client: str, selfsigned_cert: tuple):
"""Test upload certificate."""
"""Test upload certificate.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param selfsigned_cert: Selfsigned certificates
:type selfsigned_cert: tuple
"""
admin.realm_name = realm admin.realm_name = realm
cert, _ = selfsigned_cert cert, _ = selfsigned_cert
cert = cert.decode("utf-8").strip() cert = cert.decode("utf-8").strip()

110
tests/test_keycloak_openid.py

@ -22,7 +22,11 @@ from keycloak.keycloak_openid import KeycloakOpenID
def test_keycloak_openid_init(env): def test_keycloak_openid_init(env):
"""Test KeycloakOpenId's init method."""
"""Test KeycloakOpenId's init method.
:param env: Environment fixture
:type env: KeycloakTestEnv
"""
oid = KeycloakOpenID( oid = KeycloakOpenID(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
realm_name="master", realm_name="master",
@ -37,7 +41,11 @@ def test_keycloak_openid_init(env):
def test_well_known(oid: KeycloakOpenID): def test_well_known(oid: KeycloakOpenID):
"""Test the well_known method."""
"""Test the well_known method.
:param oid: Keycloak OpenID client
:type oid: KeycloakOpenID
"""
res = oid.well_known() res = oid.well_known()
assert res is not None assert res is not None
assert res != dict() assert res != dict()
@ -100,7 +108,13 @@ def test_well_known(oid: KeycloakOpenID):
def test_auth_url(env, oid: KeycloakOpenID): def test_auth_url(env, oid: KeycloakOpenID):
"""Test the auth_url method."""
"""Test the auth_url method.
:param env: Environment fixture
:type env: KeycloakTestEnv
:param oid: Keycloak OpenID client
:type oid: KeycloakOpenID
"""
res = oid.auth_url(redirect_uri="http://test.test/*") res = oid.auth_url(redirect_uri="http://test.test/*")
assert ( assert (
res res
@ -111,7 +125,11 @@ def test_auth_url(env, oid: KeycloakOpenID):
def test_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]): def test_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
"""Test the token method."""
"""Test the token method.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials oid, username, password = oid_with_credentials
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)
assert token == { assert token == {
@ -155,7 +173,13 @@ def test_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
def test_exchange_token( def test_exchange_token(
oid_with_credentials: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin oid_with_credentials: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
): ):
"""Test the exchange token method."""
"""Test the exchange token method.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
# Verify existing user # Verify existing user
oid, username, password = oid_with_credentials oid, username, password = oid_with_credentials
@ -197,7 +221,11 @@ def test_exchange_token(
def test_logout(oid_with_credentials): def test_logout(oid_with_credentials):
"""Test logout."""
"""Test logout.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials oid, username, password = oid_with_credentials
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)
@ -209,19 +237,34 @@ def test_logout(oid_with_credentials):
def test_certs(oid: KeycloakOpenID): def test_certs(oid: KeycloakOpenID):
"""Test certificates."""
"""Test certificates.
:param oid: Keycloak OpenID client
:type oid: KeycloakOpenID
"""
assert len(oid.certs()["keys"]) == 2 assert len(oid.certs()["keys"]) == 2
def test_public_key(oid: KeycloakOpenID): def test_public_key(oid: KeycloakOpenID):
"""Test public key."""
"""Test public key.
:param oid: Keycloak OpenID client
:type oid: KeycloakOpenID
"""
assert oid.public_key() is not None assert oid.public_key() is not None
def test_entitlement( def test_entitlement(
oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
): ):
"""Test entitlement."""
"""Test entitlement.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
oid, username, password = oid_with_credentials_authz oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)
resource_server_id = admin.get_client_authz_resources( resource_server_id = admin.get_client_authz_resources(
@ -233,7 +276,11 @@ def test_entitlement(
def test_introspect(oid_with_credentials: Tuple[KeycloakOpenID, str, str]): def test_introspect(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
"""Test introspect."""
"""Test introspect.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials oid, username, password = oid_with_credentials
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)
@ -247,7 +294,11 @@ def test_introspect(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
def test_decode_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]): def test_decode_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
"""Test decode token."""
"""Test decode token.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials oid, username, password = oid_with_credentials
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)
@ -262,7 +313,12 @@ def test_decode_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
def test_load_authorization_config(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]): def test_load_authorization_config(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]):
"""Test load authorization config."""
"""Test load authorization config.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials_authz oid, username, password = oid_with_credentials_authz
oid.load_authorization_config(path="tests/data/authz_settings.json") oid.load_authorization_config(path="tests/data/authz_settings.json")
@ -277,7 +333,12 @@ def test_load_authorization_config(oid_with_credentials_authz: Tuple[KeycloakOpe
def test_get_policies(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]): def test_get_policies(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]):
"""Test get policies."""
"""Test get policies.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials_authz oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)
@ -310,7 +371,12 @@ def test_get_policies(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str
def test_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]): def test_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]):
"""Test get policies."""
"""Test get policies.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials_authz oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)
@ -354,7 +420,12 @@ def test_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str,
def test_uma_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]): def test_uma_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]):
"""Test UMA permissions."""
"""Test UMA permissions.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials_authz oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)
@ -365,7 +436,14 @@ def test_uma_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str,
def test_has_uma_access( def test_has_uma_access(
oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
): ):
"""Test has UMA access."""
"""Test has UMA access.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
oid, username, password = oid_with_credentials_authz oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password) token = oid.token(username=username, password=password)

7
tox.ini

@ -13,6 +13,7 @@ commands =
black --check --diff src/keycloak tests docs black --check --diff src/keycloak tests docs
isort -c --df src/keycloak tests docs isort -c --df src/keycloak tests docs
flake8 src/keycloak tests docs flake8 src/keycloak tests docs
codespell src tests docs
[testenv:apply-check] [testenv:apply-check]
commands = commands =
@ -44,9 +45,13 @@ commands =
setenv = file|tox.env setenv = file|tox.env
passenv = CONTAINER_HOST passenv = CONTAINER_HOST
commands = commands =
cz changelog --incremental
cz changelog
[flake8] [flake8]
max-line-length = 99 max-line-length = 99
docstring-convention = all docstring-convention = all
ignore = D203, D213, W503 ignore = D203, D213, W503
docstring_style = sphinx
[darglint]
enable = DAR104
Loading…
Cancel
Save