Browse Source

feat: functions for updating resource permissions and getting associated policies for a permission (#574)

* fix: tox.ini config fixed for testenv:check

* feat: functions for updating reource permissions and getting associated policies

* fix: linting issues resolved

* revert: brought back all functions which were mistakenly removed in commit

* fix: linting issue resolved to prevent unintended changes in file

* fix: comments fixed for docs

* fix: async functions created for new functionality

* feat: test cases completed for new functionality

* chore: tox and deps update

---------

Co-authored-by: Richard Nemeth <ryshoooo@gmail.com>
pull/576/head v4.2.0
Mohsin Anees 6 months ago
committed by GitHub
parent
commit
fa1ce3bb2b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 70
      poetry.lock
  2. 122
      src/keycloak/keycloak_admin.py
  3. 6
      src/keycloak/urls_patterns.py
  4. 54
      tests/test_keycloak_admin.py
  5. 3
      tox.ini

70
poetry.lock

@ -632,34 +632,34 @@ test = ["pytest (>=6)"]
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.15.1"
version = "3.15.3"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "filelock-3.15.1-py3-none-any.whl", hash = "sha256:71b3102950e91dfc1bb4209b64be4dc8854f40e5f534428d8684f953ac847fac"},
{file = "filelock-3.15.1.tar.gz", hash = "sha256:58a2549afdf9e02e10720eaa4d4470f56386d7a6f72edd7d0596337af8ed7ad8"},
{file = "filelock-3.15.3-py3-none-any.whl", hash = "sha256:0151273e5b5d6cf753a61ec83b3a9b7d8821c39ae9af9d7ecf2f9e2f17404103"},
{file = "filelock-3.15.3.tar.gz", hash = "sha256:e1199bf5194a2277273dacd50269f0d87d0682088a3c561c15674ea9005d8635"},
] ]
[package.extras] [package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
typing = ["typing-extensions (>=4.8)"] typing = ["typing-extensions (>=4.8)"]
[[package]] [[package]]
name = "flake8" name = "flake8"
version = "7.0.0"
version = "7.1.0"
description = "the modular source code checker: pep8 pyflakes and co" description = "the modular source code checker: pep8 pyflakes and co"
optional = false optional = false
python-versions = ">=3.8.1" python-versions = ">=3.8.1"
files = [ files = [
{file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"},
{file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"},
{file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"},
{file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"},
] ]
[package.dependencies] [package.dependencies]
mccabe = ">=0.7.0,<0.8.0" mccabe = ">=0.7.0,<0.8.0"
pycodestyle = ">=2.11.0,<2.12.0"
pycodestyle = ">=2.12.0,<2.13.0"
pyflakes = ">=3.2.0,<3.3.0" pyflakes = ">=3.2.0,<3.3.0"
[[package]] [[package]]
@ -785,22 +785,22 @@ files = [
[[package]] [[package]]
name = "importlib-metadata" name = "importlib-metadata"
version = "7.1.0"
version = "7.2.0"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
{file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
{file = "importlib_metadata-7.2.0-py3-none-any.whl", hash = "sha256:04e4aad329b8b948a5711d394fa8759cb80f009225441b4f2a02bd4d8e5f426c"},
{file = "importlib_metadata-7.2.0.tar.gz", hash = "sha256:3ff4519071ed42740522d494d04819b666541b9752c43012f85afb2cc220fcc6"},
] ]
[package.dependencies] [package.dependencies]
zipp = ">=0.5" zipp = ">=0.5"
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"] perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
[[package]] [[package]]
name = "importlib-resources" name = "importlib-resources"
@ -1272,13 +1272,13 @@ wcwidth = "*"
[[package]] [[package]]
name = "pycodestyle" name = "pycodestyle"
version = "2.11.1"
version = "2.12.0"
description = "Python style guide checker" description = "Python style guide checker"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
{file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
{file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"},
{file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"},
] ]
[[package]] [[package]]
@ -1336,22 +1336,22 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]] [[package]]
name = "pyproject-api" name = "pyproject-api"
version = "1.6.1"
version = "1.7.1"
description = "API to interact with the python pyproject.toml based projects" description = "API to interact with the python pyproject.toml based projects"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pyproject_api-1.6.1-py3-none-any.whl", hash = "sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675"},
{file = "pyproject_api-1.6.1.tar.gz", hash = "sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538"},
{file = "pyproject_api-1.7.1-py3-none-any.whl", hash = "sha256:2dc1654062c2b27733d8fd4cdda672b22fe8741ef1dde8e3a998a9547b071eeb"},
{file = "pyproject_api-1.7.1.tar.gz", hash = "sha256:7ebc6cd10710f89f4cf2a2731710a98abce37ebff19427116ff2174c9236a827"},
] ]
[package.dependencies] [package.dependencies]
packaging = ">=23.1"
packaging = ">=24.1"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
docs = ["furo (>=2023.8.19)", "sphinx (<7.2)", "sphinx-autodoc-typehints (>=1.24)"]
testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "setuptools (>=68.1.2)", "wheel (>=0.41.2)"]
docs = ["furo (>=2024.5.6)", "sphinx-autodoc-typehints (>=2.2.1)"]
testing = ["covdefaults (>=2.3)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=70.1)"]
[[package]] [[package]]
name = "pytest" name = "pytest"
@ -1657,18 +1657,18 @@ jeepney = ">=0.6"
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "70.0.0"
version = "70.1.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"},
{file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"},
{file = "setuptools-70.1.0-py3-none-any.whl", hash = "sha256:d9b8b771455a97c8a9f3ab3448ebe0b29b5e105f1228bba41028be116985a267"},
{file = "setuptools-70.1.0.tar.gz", hash = "sha256:01a1e793faa5bd89abc851fa15d0a0db26f160890c7102cd8dce643e886b47f5"},
] ]
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
[[package]] [[package]]
name = "six" name = "six"
@ -1740,13 +1740,13 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"]
[[package]] [[package]]
name = "sphinx-autoapi" name = "sphinx-autoapi"
version = "3.1.1"
version = "3.1.2"
description = "Sphinx API documentation generator" description = "Sphinx API documentation generator"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "sphinx_autoapi-3.1.1-py2.py3-none-any.whl", hash = "sha256:ff202754c38e119fe60b9336fb3cdbd0b78f8623753fa9151aed42a7052506b3"},
{file = "sphinx_autoapi-3.1.1.tar.gz", hash = "sha256:b5f6e3c61cd86c0cdb7ee77a9d580c0fd116726c5b29cdcb1f1d5f30a5bca1bd"},
{file = "sphinx_autoapi-3.1.2-py2.py3-none-any.whl", hash = "sha256:8d672bd2baa8365ac844d3f52c0d3360aa492299131d3dea156a20a26f048d23"},
{file = "sphinx_autoapi-3.1.2.tar.gz", hash = "sha256:fa5eb188f67ae39e19b2e7d2527c75d064e0f0b9ac7f77a3558ec26ccb731c26"},
] ]
[package.dependencies] [package.dependencies]
@ -1981,13 +1981,13 @@ files = [
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.2.1"
version = "2.2.2"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
] ]
[package.extras] [package.extras]
@ -1998,13 +1998,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.26.2"
version = "20.26.3"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"},
{file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"},
{file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"},
{file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"},
] ]
[package.dependencies] [package.dependencies]

122
src/keycloak/keycloak_admin.py

@ -4015,6 +4015,43 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201]) return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201])
def update_client_authz_resource_permission(self, payload, client_id, resource_id):
"""Update permissions for a given resource.
Payload example::
payload={
"id": resource_id,
"name": "My Permission Name",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"resources": [some_resource_id],
"scopes": [],
"policies": [some_policy_id],
}
:param payload: No Document
:type payload: dict
:param client_id: id in ClientRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation
:type client_id: str
:param resource_id: No Document
:type resource_id: str
:return: Keycloak server response
:rtype: bytes
"""
params_path = {
"realm-name": self.connection.realm_name,
"id": client_id,
"resource-id": resource_id,
}
data_raw = self.connection.raw_put(
urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_PERMISSION.format(**params_path),
data=json.dumps(payload),
)
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201])
def get_client_authz_client_policies(self, client_id): def get_client_authz_client_policies(self, client_id):
"""Get policies for a given client. """Get policies for a given client.
@ -4030,6 +4067,30 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
def get_client_authz_permission_associated_policies(self, client_id, policy_id):
"""Get associated policies for a given client permission.
:param client_id: id in ClientRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation
:type client_id: str
:param policy_id: id in PolicyRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_policyrepresentation
:type policy_id: str
:return: Keycloak server response (RoleRepresentation)
:rtype: list
"""
params_path = {
"realm-name": self.connection.realm_name,
"id": client_id,
"policy-id": policy_id,
}
data_raw = self.connection.raw_get(
urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY_ASSOCIATED_POLICIES.format(
**params_path
)
)
return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
def create_client_authz_client_policy(self, payload, client_id): def create_client_authz_client_policy(self, payload, client_id):
"""Create a new policy for a given client. """Create a new policy for a given client.
@ -8152,6 +8213,43 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201]) return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201])
async def a_update_client_authz_resource_permission(self, payload, client_id, resource_id):
"""Update permissions for a given resource asynchronously.
Payload example::
payload={
"id": resource_id,
"name": "My Permission Name",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"resources": [some_resource_id],
"scopes": [],
"policies": [some_policy_id],
}
:param payload: No Document
:type payload: dict
:param client_id: id in ClientRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation
:type client_id: str
:param resource_id: No Document
:type resource_id: str
:return: Keycloak server response
:rtype: bytes
"""
params_path = {
"realm-name": self.connection.realm_name,
"id": client_id,
"resource-id": resource_id,
}
data_raw = await self.connection.a_raw_put(
urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_PERMISSION.format(**params_path),
data=json.dumps(payload),
)
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201])
async def a_get_client_authz_client_policies(self, client_id): async def a_get_client_authz_client_policies(self, client_id):
"""Get policies for a given client asynchronously. """Get policies for a given client asynchronously.
@ -8167,6 +8265,30 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
async def a_get_client_authz_permission_associated_policies(self, client_id, policy_id):
"""Get associated policies for a given client permission asynchronously.
:param client_id: id in ClientRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation
:type client_id: str
:param policy_id: id in PolicyRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_policyrepresentation
:type policy_id: str
:return: Keycloak server response (RoleRepresentation)
:rtype: list
"""
params_path = {
"realm-name": self.connection.realm_name,
"id": client_id,
"policy-id": policy_id,
}
data_raw = await self.connection.a_raw_get(
urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY_ASSOCIATED_POLICIES.format(
**params_path
)
)
return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
async def a_create_client_authz_client_policy(self, payload, client_id): async def a_create_client_authz_client_policy(self, payload, client_id):
"""Create a new policy for a given client asynchronously. """Create a new policy for a given client asynchronously.

6
src/keycloak/urls_patterns.py

@ -129,8 +129,14 @@ URL_ADMIN_CLIENT_AUTHZ_POLICY = URL_ADMIN_CLIENT_AUTHZ + "/policy/{policy-id}"
URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES = URL_ADMIN_CLIENT_AUTHZ_POLICY + "/scopes" URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES = URL_ADMIN_CLIENT_AUTHZ_POLICY + "/scopes"
URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES = URL_ADMIN_CLIENT_AUTHZ_POLICY + "/resources" URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES = URL_ADMIN_CLIENT_AUTHZ_POLICY + "/resources"
URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION = URL_ADMIN_CLIENT_AUTHZ + "/permission/scope/{scope-id}" URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION = URL_ADMIN_CLIENT_AUTHZ + "/permission/scope/{scope-id}"
URL_ADMIN_CLIENT_AUTHZ_RESOURCE_PERMISSION = (
URL_ADMIN_CLIENT_AUTHZ + "/permission/resource/{resource-id}"
)
URL_ADMIN_ADD_CLIENT_AUTHZ_SCOPE_PERMISSION = URL_ADMIN_CLIENT_AUTHZ + "/permission/scope?max=-1" URL_ADMIN_ADD_CLIENT_AUTHZ_SCOPE_PERMISSION = URL_ADMIN_CLIENT_AUTHZ + "/permission/scope?max=-1"
URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY = URL_ADMIN_CLIENT_AUTHZ + "/policy/client" URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY = URL_ADMIN_CLIENT_AUTHZ + "/policy/client"
URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY_ASSOCIATED_POLICIES = (
URL_ADMIN_CLIENT_AUTHZ + "/policy/{policy-id}/associatedPolicies"
)
URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER = URL_ADMIN_CLIENT + "/service-account-user" URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER = URL_ADMIN_CLIENT + "/service-account-user"
URL_ADMIN_CLIENT_CERTS = URL_ADMIN_CLIENT + "/certificates/{attr}" URL_ADMIN_CLIENT_CERTS = URL_ADMIN_CLIENT + "/certificates/{attr}"

54
tests/test_keycloak_admin.py

@ -1102,6 +1102,8 @@ def test_clients(admin: KeycloakAdmin, realm: str):
payload={"name": "test-authz-rb-policy", "roles": [{"id": role_id}]}, payload={"name": "test-authz-rb-policy", "roles": [{"id": role_id}]},
) )
assert res["name"] == "test-authz-rb-policy", res assert res["name"] == "test-authz-rb-policy", res
role_based_policy_id = res["id"]
role_based_policy_name = res["name"]
with pytest.raises(KeycloakPostError) as err: with pytest.raises(KeycloakPostError) as err:
admin.create_client_authz_role_based_policy( admin.create_client_authz_role_based_policy(
@ -1174,6 +1176,8 @@ def test_clients(admin: KeycloakAdmin, realm: str):
assert res, res assert res, res
assert res["name"] == "test-permission-rb" assert res["name"] == "test-permission-rb"
assert res["resources"] == [test_resource_id] assert res["resources"] == [test_resource_id]
resource_based_permission_id = res["id"]
resource_based_permission_name = res["name"]
with pytest.raises(KeycloakPostError) as err: with pytest.raises(KeycloakPostError) as err:
admin.create_client_authz_resource_based_permission( admin.create_client_authz_resource_based_permission(
@ -1188,6 +1192,29 @@ def test_clients(admin: KeycloakAdmin, realm: str):
) == {"msg": "Already exists"} ) == {"msg": "Already exists"}
assert len(admin.get_client_authz_permissions(client_id=auth_client_id)) == 2 assert len(admin.get_client_authz_permissions(client_id=auth_client_id)) == 2
# Test associating client policy with resource based permission
res = admin.update_client_authz_resource_permission(
client_id=auth_client_id,
resource_id=resource_based_permission_id,
payload={
"id": resource_based_permission_id,
"name": resource_based_permission_name,
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"resources": [test_resource_id],
"scopes": [],
"policies": [role_based_policy_id],
},
)
# Test getting associated policies for a permission
associated_policies = admin.get_client_authz_permission_associated_policies(
client_id=auth_client_id, policy_id=resource_based_permission_id
)
assert len(associated_policies) == 1
assert associated_policies[0]["name"].startswith(role_based_policy_name)
# Test authz scopes # Test authz scopes
res = admin.get_client_authz_scopes(client_id=auth_client_id) res = admin.get_client_authz_scopes(client_id=auth_client_id)
assert len(res) == 0, res assert len(res) == 0, res
@ -4102,6 +4129,8 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str):
skip_exists=True, skip_exists=True,
) == {"msg": "Already exists"} ) == {"msg": "Already exists"}
assert len(await admin.a_get_client_authz_policies(client_id=auth_client_id)) == 2 assert len(await admin.a_get_client_authz_policies(client_id=auth_client_id)) == 2
role_based_policy_id = res["id"]
role_based_policy_name = res["name"]
res = await admin.a_create_client_authz_role_based_policy( res = await admin.a_create_client_authz_role_based_policy(
client_id=auth_client_id, client_id=auth_client_id,
@ -4161,6 +4190,8 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str):
assert res, res assert res, res
assert res["name"] == "test-permission-rb" assert res["name"] == "test-permission-rb"
assert res["resources"] == [test_resource_id] assert res["resources"] == [test_resource_id]
resource_based_permission_id = res["id"]
resource_based_permission_name = res["name"]
with pytest.raises(KeycloakPostError) as err: with pytest.raises(KeycloakPostError) as err:
await admin.a_create_client_authz_resource_based_permission( await admin.a_create_client_authz_resource_based_permission(
@ -4175,6 +4206,29 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str):
) == {"msg": "Already exists"} ) == {"msg": "Already exists"}
assert len(await admin.a_get_client_authz_permissions(client_id=auth_client_id)) == 2 assert len(await admin.a_get_client_authz_permissions(client_id=auth_client_id)) == 2
# Test associating client policy with resource based permission
res = await admin.a_update_client_authz_resource_permission(
client_id=auth_client_id,
resource_id=resource_based_permission_id,
payload={
"id": resource_based_permission_id,
"name": resource_based_permission_name,
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"resources": [test_resource_id],
"scopes": [],
"policies": [role_based_policy_id],
},
)
# Test getting associated policies for a permission
associated_policies = await admin.a_get_client_authz_permission_associated_policies(
client_id=auth_client_id, policy_id=resource_based_permission_id
)
assert len(associated_policies) == 1
assert associated_policies[0]["name"].startswith(role_based_policy_name)
# Test authz scopes # Test authz scopes
res = await admin.a_get_client_authz_scopes(client_id=auth_client_id) res = await admin.a_get_client_authz_scopes(client_id=auth_client_id)
assert len(res) == 0, res assert len(res) == 0, res

3
tox.ini

@ -14,19 +14,16 @@ commands =
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 codespell src tests docs
allowlist_externals = black, poetry
[testenv:apply-check] [testenv:apply-check]
commands = commands =
black -C src/keycloak tests docs black -C src/keycloak tests docs
black src/keycloak tests docs black src/keycloak tests docs
isort src/keycloak tests docs isort src/keycloak tests docs
allowlist_externals = black, poetry, isort
[testenv:docs] [testenv:docs]
commands = commands =
sphinx-build -T -E -W -b html -d _build/doctrees -D language=en ./docs/source _build/html sphinx-build -T -E -W -b html -d _build/doctrees -D language=en ./docs/source _build/html
allowlist_externals = sphinx-build, poetry
[testenv:tests] [testenv:tests]
setenv = file|tox.env setenv = file|tox.env

Loading…
Cancel
Save