Browse Source

style: start of more checks

pull/373/head
Richard Nemeth 2 years ago
parent
commit
2bf150f7c1
No known key found for this signature in database GPG Key ID: 21C39470DF3DEC39
  1. 1
      docs/source/conf.py
  2. 38
      poetry.lock
  3. 5
      pyproject.toml
  4. 8
      src/keycloak/authorization/__init__.py
  5. 69
      src/keycloak/authorization/permission.py
  6. 78
      src/keycloak/authorization/policy.py
  7. 27
      src/keycloak/authorization/role.py
  8. 115
      src/keycloak/connection.py
  9. 2
      src/keycloak/exceptions.py
  10. 192
      src/keycloak/keycloak_admin.py
  11. 2
      tests/test_keycloak_admin.py
  12. 5
      tox.ini

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

38
poetry.lock

@ -140,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.1.0"
description = "Codespell"
category = "dev"
optional = false
python-versions = ">=3.5"
[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"
@ -212,6 +224,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"
@ -291,7 +311,7 @@ pydocstyle = ">=2.1"
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.5.1"
version = "2.5.2"
description = "File identification library for Python" description = "File identification library for Python"
category = "dev" category = "dev"
optional = false optional = false
@ -731,7 +751,7 @@ requests = ">=2.0.1,<3.0.0"
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "4.8"
version = "4.9"
description = "Pure-Python RSA implementation" description = "Pure-Python RSA implementation"
category = "main" category = "main"
optional = false optional = false
@ -1039,7 +1059,7 @@ 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 = [ alabaster = [
@ -1075,6 +1095,7 @@ click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
] ]
codespell = []
colorama = [ colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
@ -1086,6 +1107,7 @@ commonmark = [
] ]
coverage = [] coverage = []
cryptography = [] cryptography = []
darglint = []
decli = [ decli = [
{file = "decli-0.5.2-py3-none-any.whl", hash = "sha256:d3207bc02d0169bf6ed74ccca09ce62edca0eb25b0ebf8bf4ae3fb8333e15ca0"}, {file = "decli-0.5.2-py3-none-any.whl", hash = "sha256:d3207bc02d0169bf6ed74ccca09ce62edca0eb25b0ebf8bf4ae3fb8333e15ca0"},
{file = "decli-0.5.2.tar.gz", hash = "sha256:f2cde55034a75c819c630c7655a844c612f2598c42c21299160465df6ad463ad"}, {file = "decli-0.5.2.tar.gz", hash = "sha256:f2cde55034a75c819c630c7655a844c612f2598c42c21299160465df6ad463ad"},
@ -1105,10 +1127,7 @@ flake8 = [
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
] ]
flake8-docstrings = [] flake8-docstrings = []
identify = [
{file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"},
{file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"},
]
identify = []
idna = [ idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
@ -1355,10 +1374,7 @@ recommonmark = [
] ]
requests = [] requests = []
requests-toolbelt = [] requests-toolbelt = []
rsa = [
{file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"},
{file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"},
]
rsa = []
six = [ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},

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,

2
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

192
src/keycloak/keycloak_admin.py

@ -49,19 +49,31 @@ class KeycloakAdmin:
"""Keycloak Admin client. """Keycloak Admin client.
:param server_url: Keycloak server url :param server_url: Keycloak server url
:type server_url: str
:param username: admin username :param username: admin username
:type username: str
:param password: admin password :param password: admin password
:type password: str
:param totp: Time based OTP :param totp: Time based OTP
:type totp: str
:param realm_name: realm name :param realm_name: realm name
:type realm_name: str
:param client_id: client id :param client_id: client id
:type client_id: str
:param verify: True if want check connection SSL :param verify: True if want check connection SSL
:type verify: bool
:param client_secret_key: client secret key :param client_secret_key: client secret key
(optional, required only for access type confidential) (optional, required only for access type confidential)
:type client_secret_key: str
:param custom_headers: dict of custom header to pass to each HTML request :param custom_headers: dict of custom header to pass to each HTML request
:type custom_headers: dict
:param user_realm_name: The realm name of the user, if different from realm_name :param user_realm_name: The realm name of the user, if different from realm_name
:type user_realm_name: str
:param auto_refresh_token: list of methods that allows automatic token refresh. :param auto_refresh_token: list of methods that allows automatic token refresh.
Ex: ['get', 'put', 'post', 'delete'] Ex: ['get', 'put', 'post', 'delete']
:type auto_refresh_token: list
:param timeout: connection timeout in seconds :param timeout: connection timeout in seconds
:type timeout: int
""" """
PAGE_SIZE = 100 PAGE_SIZE = 100
@ -95,7 +107,35 @@ class KeycloakAdmin:
auto_refresh_token=None, auto_refresh_token=None,
timeout=60, timeout=60,
): ):
"""Init method."""
"""Init method.
:param server_url: Keycloak server url
:type server_url: str
:param username: admin username
:type username: str
:param password: admin password
:type password: str
:param totp: Time based OTP
:type totp: str
:param realm_name: realm name
:type realm_name: str
:param client_id: client id
:type client_id: str
:param verify: True if want check connection SSL
:type verify: bool
:param client_secret_key: client secret key
(optional, required only for access type confidential)
:type client_secret_key: str
:param custom_headers: dict of custom header to pass to each HTML request
:type custom_headers: dict
:param user_realm_name: The realm name of the user, if different from realm_name
:type user_realm_name: str
:param auto_refresh_token: list of methods that allows automatic token refresh.
Ex: ['get', 'put', 'post', 'delete']
:type auto_refresh_token: list
:param timeout: connection timeout in seconds
:type timeout: int
"""
self.server_url = server_url self.server_url = server_url
self.username = username self.username = username
self.password = password self.password = password
@ -114,7 +154,11 @@ class KeycloakAdmin:
@property @property
def server_url(self): def server_url(self):
"""Get server url."""
"""Get server url.
:returns: Keycloak server url
:rtype: str
"""
return self._server_url return self._server_url
@server_url.setter @server_url.setter
@ -123,7 +167,11 @@ class KeycloakAdmin:
@property @property
def realm_name(self): def realm_name(self):
"""Get realm name."""
"""Get realm name.
:returns: Realm name
:rtype: str
"""
return self._realm_name return self._realm_name
@realm_name.setter @realm_name.setter
@ -132,7 +180,11 @@ class KeycloakAdmin:
@property @property
def connection(self): def connection(self):
"""Get connection."""
"""Get connection.
:returns: Connection manager
:rtype: ConnectionManager
"""
return self._connection return self._connection
@connection.setter @connection.setter
@ -141,7 +193,11 @@ class KeycloakAdmin:
@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
@ -150,7 +206,11 @@ class KeycloakAdmin:
@property @property
def client_secret_key(self): def client_secret_key(self):
"""Get client secret key."""
"""Get 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
@ -159,7 +219,11 @@ class KeycloakAdmin:
@property @property
def verify(self): def verify(self):
"""Get verify."""
"""Get verify.
:returns: Verify indicator
:rtype: bool
"""
return self._verify return self._verify
@verify.setter @verify.setter
@ -168,7 +232,11 @@ class KeycloakAdmin:
@property @property
def username(self): def username(self):
"""Get username."""
"""Get username.
:returns: Admin username
:rtype: str
"""
return self._username return self._username
@username.setter @username.setter
@ -177,7 +245,11 @@ class KeycloakAdmin:
@property @property
def password(self): def password(self):
"""Get password."""
"""Get password.
:returns: Admin password
:rtype: str
"""
return self._password return self._password
@password.setter @password.setter
@ -186,7 +258,11 @@ class KeycloakAdmin:
@property @property
def totp(self): def totp(self):
"""Get totp."""
"""Get totp.
:returns: TOTP
:rtype: str
"""
return self._totp return self._totp
@totp.setter @totp.setter
@ -195,7 +271,11 @@ class KeycloakAdmin:
@property @property
def token(self): def token(self):
"""Get token."""
"""Get token.
:returns: Access and refresh token
:rtype: dict
"""
return self._token return self._token
@token.setter @token.setter
@ -204,12 +284,20 @@ class KeycloakAdmin:
@property @property
def auto_refresh_token(self): def auto_refresh_token(self):
"""Get auto refresh token."""
"""Get auto refresh token.
:returns: List of methods for automatic token refresh
:rtype: list
"""
return self._auto_refresh_token return self._auto_refresh_token
@property @property
def user_realm_name(self): def user_realm_name(self):
"""Get user realm name."""
"""Get user realm name.
:returns: User realm name
:rtype: str
"""
return self._user_realm_name return self._user_realm_name
@user_realm_name.setter @user_realm_name.setter
@ -218,7 +306,11 @@ class KeycloakAdmin:
@property @property
def custom_headers(self): def custom_headers(self):
"""Get custom headers."""
"""Get custom headers.
:returns: Custom headers
:rtype: dict
"""
return self._custom_headers return self._custom_headers
@custom_headers.setter @custom_headers.setter
@ -247,13 +339,16 @@ class KeycloakAdmin:
Wrapper function to paginate GET requests. Wrapper function to paginate GET requests.
:param url: The url on which the query is executed :param url: The url on which the query is executed
:type url: str
:param query: Existing query parameters (optional) :param query: Existing query parameters (optional)
:type query: dict
:return: Combined results of paginated queries :return: Combined results of paginated queries
:rtype: list
""" """
results = [] results = []
# initalize query if it was called with None
# initialize query if it was called with None
if not query: if not query:
query = {} query = {}
page = 0 page = 0
@ -274,8 +369,16 @@ class KeycloakAdmin:
return results return results
def __fetch_paginated(self, url, query=None): def __fetch_paginated(self, url, query=None):
query = query or {}
"""Make a specific paginated request.
:param url: The url on which the query is executed
:type url: str
:param query: Pagination settings
:type query: dict
:returns: Response
:rtype: dict
"""
query = query or {}
return raise_error_from_response(self.raw_get(url, **query), KeycloakGetError) return raise_error_from_response(self.raw_get(url, **query), KeycloakGetError)
def import_realm(self, payload): def import_realm(self, payload):
@ -287,8 +390,9 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
:param payload: RealmRepresentation :param payload: RealmRepresentation
:type payload: dict
:return: RealmRepresentation :return: RealmRepresentation
:rtype: dict
""" """
data_raw = self.raw_post(urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload)) data_raw = self.raw_post(urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload))
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
@ -299,10 +403,13 @@ class KeycloakAdmin:
RealmRepresentation RealmRepresentation
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_partialexport https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_partialexport
:param export-clients: Skip if not want to export realm clients
:param export-groups-and-roles: Skip if not want to export realm groups and roles
:param export_clients: Skip if not want to export realm clients
:type export_clients: bool
:param export_groups_and_role: Skip if not want to export realm groups and roles
:type export_groups_and_role: bool
:return: realm configurations JSON :return: realm configurations JSON
:rtype: dict
""" """
params_path = { params_path = {
"realm-name": self.realm_name, "realm-name": self.realm_name,
@ -318,6 +425,7 @@ class KeycloakAdmin:
"""List all realms in Keycloak deployment. """List all realms in Keycloak deployment.
:return: realms list :return: realms list
:rtype: list
""" """
data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALMS) data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALMS)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
@ -329,7 +437,9 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_realmrepresentation https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_realmrepresentation
:param realm_name: Realm name (not the realm id) :param realm_name: Realm name (not the realm id)
:type realm_name: str
:return: RealmRepresentation :return: RealmRepresentation
:rtype: dict
""" """
params_path = {"realm-name": realm_name} params_path = {"realm-name": realm_name}
data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALM.format(**params_path)) data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALM.format(**params_path))
@ -342,8 +452,11 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
:param payload: RealmRepresentation :param payload: RealmRepresentation
:type payload: dict
:param skip_exists: Skip if Realm already exist. :param skip_exists: Skip if Realm already exist.
:type skip_exists: bool
:return: Keycloak server response (RealmRepresentation) :return: Keycloak server response (RealmRepresentation)
:rtype: dict
""" """
data_raw = self.raw_post(urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload)) data_raw = self.raw_post(urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload))
return raise_error_from_response( return raise_error_from_response(
@ -353,15 +466,18 @@ class KeycloakAdmin:
def update_realm(self, realm_name, payload): def update_realm(self, realm_name, payload):
"""Update a realm. """Update a realm.
This wil only update top level attributes and will ignore any user,
This will only update top level attributes and will ignore any user,
role, or client information in the payload. role, or client information in the payload.
RealmRepresentation: RealmRepresentation:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
:param realm_name: Realm name (not the realm id) :param realm_name: Realm name (not the realm id)
:type realm_name: str
:param payload: RealmRepresentation :param payload: RealmRepresentation
:type payload: dict
:return: Http response :return: Http response
:rtype: dict
""" """
params_path = {"realm-name": realm_name} params_path = {"realm-name": realm_name}
data_raw = self.raw_put( data_raw = self.raw_put(
@ -373,7 +489,9 @@ class KeycloakAdmin:
"""Delete a realm. """Delete a realm.
:param realm_name: Realm name (not the realm id) :param realm_name: Realm name (not the realm id)
:type realm_name: str
:return: Http response :return: Http response
:rtype: dict
""" """
params_path = {"realm-name": realm_name} params_path = {"realm-name": realm_name}
data_raw = self.raw_delete(urls_patterns.URL_ADMIN_REALM.format(**params_path)) data_raw = self.raw_delete(urls_patterns.URL_ADMIN_REALM.format(**params_path))
@ -388,7 +506,9 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
:param query: Query parameters (optional) :param query: Query parameters (optional)
:type query: dict
:return: users list :return: users list
:rtype: list
""" """
query = query or {} query = query or {}
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
@ -406,6 +526,9 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation
:param: payload: IdentityProviderRepresentation :param: payload: IdentityProviderRepresentation
:type payload: dict
:returns: Keycloak server response
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
data_raw = self.raw_post( data_raw = self.raw_post(
@ -419,8 +542,12 @@ class KeycloakAdmin:
IdentityProviderRepresentation IdentityProviderRepresentation
https://www.keycloak.org/docs-api/15.0/rest-api/index.html#_identity_providers_resource https://www.keycloak.org/docs-api/15.0/rest-api/index.html#_identity_providers_resource
:param: alias: alias for IdP to update
:param: idp_alias: alias for IdP to update
:type idp_alias: str
:param: payload: The IdentityProviderRepresentation :param: payload: The IdentityProviderRepresentation
:type payload: dict
:returns: Keycloak server response
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name, "alias": idp_alias} params_path = {"realm-name": self.realm_name, "alias": idp_alias}
data_raw = self.raw_put( data_raw = self.raw_put(
@ -435,7 +562,11 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityprovidermapperrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityprovidermapperrepresentation
:param: idp_alias: alias for Idp to add mapper in :param: idp_alias: alias for Idp to add mapper in
:type idp_alias: str
:param: payload: IdentityProviderMapperRepresentation :param: payload: IdentityProviderMapperRepresentation
:type payload: dict
:returns: Keycloak server response
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name, "idp-alias": idp_alias} params_path = {"realm-name": self.realm_name, "idp-alias": idp_alias}
data_raw = self.raw_post( data_raw = self.raw_post(
@ -450,9 +581,13 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_update https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_update
:param: idp_alias: alias for Idp to fetch mappers :param: idp_alias: alias for Idp to fetch mappers
:type idp_alias: str
:param: mapper_id: Mapper Id to update :param: mapper_id: Mapper Id to update
:type mapper_id: str
:param: payload: IdentityProviderMapperRepresentation :param: payload: IdentityProviderMapperRepresentation
:type payload: dict
:return: Http response :return: Http response
:rtype: dict
""" """
params_path = { params_path = {
"realm-name": self.realm_name, "realm-name": self.realm_name,
@ -476,7 +611,9 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getmappers https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getmappers
:param: idp_alias: alias for Idp to fetch mappers :param: idp_alias: alias for Idp to fetch mappers
:type idp_alias: str
:return: array IdentityProviderMapperRepresentation :return: array IdentityProviderMapperRepresentation
:rtype: list
""" """
params_path = {"realm-name": self.realm_name, "idp-alias": idp_alias} params_path = {"realm-name": self.realm_name, "idp-alias": idp_alias}
data_raw = self.raw_get(urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path)) data_raw = self.raw_get(urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path))
@ -491,6 +628,7 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation
:return: array IdentityProviderRepresentation :return: array IdentityProviderRepresentation
:rtype: list
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
data_raw = self.raw_get(urls_patterns.URL_ADMIN_IDPS.format(**params_path)) data_raw = self.raw_get(urls_patterns.URL_ADMIN_IDPS.format(**params_path))
@ -500,6 +638,9 @@ class KeycloakAdmin:
"""Delete an ID Provider. """Delete an ID Provider.
:param: idp_alias: idp alias name :param: idp_alias: idp alias name
:type idp_alias: str
:returns: Keycloak server response
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name, "alias": idp_alias} params_path = {"realm-name": self.realm_name, "alias": idp_alias}
data_raw = self.raw_delete(urls_patterns.URL_ADMIN_IDP.format(**params_path)) data_raw = self.raw_delete(urls_patterns.URL_ADMIN_IDP.format(**params_path))
@ -514,10 +655,13 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
:param payload: UserRepresentation :param payload: UserRepresentation
:type payload: dict
:param exist_ok: If False, raise KeycloakGetError if username already exists. :param exist_ok: If False, raise KeycloakGetError if username already exists.
Otherwise, return existing user ID. Otherwise, return existing user ID.
:type exist_ok: bool
:return: UserRepresentation :return: UserRepresentation
:rtype: dict
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
@ -540,8 +684,10 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_users_resource https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_users_resource
:param query: (dict) Query parameters for users count :param query: (dict) Query parameters for users count
:type query: dict
:return: counter :return: counter
:rtype: int
""" """
query = query or dict() query = query or dict()
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
@ -557,8 +703,10 @@ class KeycloakAdmin:
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
:param username: id in UserRepresentation :param username: id in UserRepresentation
:type username: str
:return: user_id :return: user_id
:rtype: str
""" """
lower_user_name = username.lower() lower_user_name = username.lower()
users = self.get_users(query={"search": lower_user_name}) users = self.get_users(query={"search": lower_user_name})
@ -2870,7 +3018,7 @@ class KeycloakAdmin:
def get_required_action_by_alias(self, action_alias): def get_required_action_by_alias(self, action_alias):
"""Get a required action by its alias. """Get a required action by its alias.
:param action_alias: the alias of the requried action.
:param action_alias: the alias of the required action.
:type action_alias: str :type action_alias: str
:return: the required action (RequiredActionProviderRepresentation). :return: the required action (RequiredActionProviderRepresentation).
:rtype: dict :rtype: dict

2
tests/test_keycloak_admin.py

@ -1820,7 +1820,7 @@ 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."""
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)

5
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 =
@ -50,3 +51,7 @@ commands =
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