mantelo 2.2.0__tar.gz → 2.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mantelo-2.2.0 → mantelo-2.2.2}/.gitignore +1 -0
- mantelo-2.2.2/.release-please-manifest.json +1 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/CHANGELOG.md +31 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/CONTRIBUTING.md +2 -1
- {mantelo-2.2.0 → mantelo-2.2.2}/Makefile +10 -5
- {mantelo-2.2.0 → mantelo-2.2.2}/PKG-INFO +12 -7
- {mantelo-2.2.0 → mantelo-2.2.2}/README.md +8 -5
- {mantelo-2.2.0 → mantelo-2.2.2}/docker-compose.yml +1 -1
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/00-getting_started.rst +2 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/index.rst +2 -2
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo/__init__.py +1 -1
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo/connection.py +11 -5
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo/exceptions.py +4 -4
- mantelo-2.2.2/mantelo/version.py +24 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo.egg-info/PKG-INFO +12 -7
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo.egg-info/SOURCES.txt +2 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo.egg-info/requires.txt +1 -0
- mantelo-2.2.2/mantelo.egg-info/scm_file_list.json +58 -0
- mantelo-2.2.2/mantelo.egg-info/scm_version.json +8 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/pyproject.toml +52 -4
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/conftest.py +6 -7
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/realms/orwell-realm.json +23 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/test_client.py +1 -1
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/test_connection.py +12 -11
- mantelo-2.2.0/.release-please-manifest.json +0 -1
- mantelo-2.2.0/mantelo/version.py +0 -16
- {mantelo-2.2.0 → mantelo-2.2.2}/.readthedocs.yaml +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/CODEOWNERS +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/LICENSE +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/MANIFEST.in +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/01-authentication.rst +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/02-making-calls.rst +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/03-examples.rst +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/04-faq.rst +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/90-api.rst +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/Makefile +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_static/css/custom.css +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_static/images/mantelo-128.png +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_static/images/mantelo-256.png +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_static/images/mantelo-512.png +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_static/images/mantelo-social-preview.png +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_static/images/mantelo-text-900.png +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_static/images/mantelo.ico +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_static/images/mantelo.svg +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_templates/.gitkeep +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/_templates/layout.html +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/conf.py +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/docs/make.bat +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo/client.py +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo/internal/__init__.py +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo/internal/api.py +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo/internal/serializers.py +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo/py.typed +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo.egg-info/dependency_links.txt +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/mantelo.egg-info/top_level.txt +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/setup.cfg +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/__init__.py +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/constants.py +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/internal/test_api.py +1 -1
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/internal/test_serialiers.py +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/tests/realms/master-realm.json +0 -0
- {mantelo-2.2.0 → mantelo-2.2.2}/tox.ini +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{".":"2.2.2"}
|
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.2.2](https://github.com/derlin/mantelo/compare/v2.2.1...v2.2.2) (2026-11-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🐛 Bug Fixes
|
|
7
|
+
|
|
8
|
+
* grant kc-admin-api scope to test client for keycloak 26.8.2 ([458eee9](https://github.com/derlin/mantelo/commit/458eee9f2ce9a4969adf2be1aa1aea8ba0349caa))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### 💬 Documentation
|
|
12
|
+
|
|
13
|
+
* state tested keycloak versions ([a0a07bc](https://github.com/derlin/mantelo/commit/a0a07bc97ea7a5092d69429fdddf1e0f30ddd663))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### 🦀 Build and CI
|
|
17
|
+
|
|
18
|
+
* keep a stable build check for branch protection ([94d655b](https://github.com/derlin/mantelo/commit/94d655b852bf28ac44d56a1730630f954a5d4ee4))
|
|
19
|
+
* test against oldest supported and latest keycloak ([55bf108](https://github.com/derlin/mantelo/commit/55bf108a0d7f096748b0a0a2bb9114dde23c0f35))
|
|
20
|
+
* update actions to latest ([764f83b](https://github.com/derlin/mantelo/commit/764f83be18aa37c249b7a15b30738fcf6e1966d8))
|
|
21
|
+
|
|
22
|
+
## [2.2.1](https://github.com/derlin/mantelo/compare/v2.2.0...v2.2.1) (2026-04-13)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### 🐛 Bug Fixes
|
|
26
|
+
|
|
27
|
+
* raise proper exception on invalid credentials on keycloak 26.6 ([1fce16a](https://github.com/derlin/mantelo/commit/1fce16ab47ce47f7f99bc9ca6460b5ae436caf21))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### 💬 Documentation
|
|
31
|
+
|
|
32
|
+
* remove duplicated `Instead,` ([1f2e3c0](https://github.com/derlin/mantelo/commit/1f2e3c0eedb79087c7d7fb28e44b9481ef65479a))
|
|
33
|
+
|
|
3
34
|
## [2.2.0](https://github.com/derlin/mantelo/compare/v2.1.1...v2.2.0) (2024-12-29)
|
|
4
35
|
|
|
5
36
|
**IMPORTANT**: This release removes the `slumber` dependency and integrates its functionality
|
|
@@ -54,7 +54,8 @@ Documentation
|
|
|
54
54
|
docs-open Open the documentation locally (requires make docs to have ran).
|
|
55
55
|
|
|
56
56
|
Development
|
|
57
|
-
lint
|
|
57
|
+
lint Check formatting and lint with ruff.
|
|
58
|
+
format Format and auto-fix with ruff.
|
|
58
59
|
test Run tests with tox (inside docker).
|
|
59
60
|
mypy Run mypy locally to check types.
|
|
60
61
|
export-realms Export test realms after changes in Keycloak Test Server.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: all help build docs lint test mypy export-realms
|
|
1
|
+
.PHONY: all help build docs lint format test mypy export-realms
|
|
2
2
|
|
|
3
3
|
default: help
|
|
4
4
|
|
|
@@ -30,13 +30,18 @@ docs-test: ## Test the documentation code snippets.
|
|
|
30
30
|
|
|
31
31
|
##@ Development
|
|
32
32
|
|
|
33
|
-
lint: ##
|
|
34
|
-
|
|
33
|
+
lint: ## Check formatting and lint with ruff.
|
|
34
|
+
ruff format --check mantelo tests
|
|
35
|
+
ruff check mantelo tests
|
|
36
|
+
|
|
37
|
+
format: ## Format and auto-fix with ruff.
|
|
38
|
+
ruff format mantelo tests
|
|
39
|
+
ruff check --fix --show-fixes mantelo tests
|
|
35
40
|
|
|
36
41
|
test: ## Run tests with tox (inside docker).
|
|
37
|
-
coverage erase
|
|
42
|
+
coverage erase || true
|
|
38
43
|
docker compose up -d --wait keycloak
|
|
39
|
-
docker compose run --
|
|
44
|
+
docker compose run tox bash -c "git config --system --add safe.directory /app && tox ${ARGS}"
|
|
40
45
|
coverage combine
|
|
41
46
|
coverage html
|
|
42
47
|
coverage xml
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: mantelo
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: ✨ Keycloak Admin REST Api Client for Python ✨
|
|
5
5
|
Author-email: Lucy Linder <lucy.derlin@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -230,6 +230,7 @@ Requires-Dist: attrs
|
|
|
230
230
|
Requires-Dist: requests
|
|
231
231
|
Provides-Extra: dev
|
|
232
232
|
Requires-Dist: build; extra == "dev"
|
|
233
|
+
Requires-Dist: ruff; extra == "dev"
|
|
233
234
|
Requires-Dist: coverage; extra == "dev"
|
|
234
235
|
Requires-Dist: tox; extra == "dev"
|
|
235
236
|
Requires-Dist: mypy; extra == "dev"
|
|
@@ -245,6 +246,7 @@ Requires-Dist: sphinx-book-theme; extra == "docs"
|
|
|
245
246
|
Requires-Dist: sphinx-autoapi; extra == "docs"
|
|
246
247
|
Requires-Dist: sphinx-copybutton; extra == "docs"
|
|
247
248
|
Requires-Dist: sphinxext-opengraph; extra == "docs"
|
|
249
|
+
Dynamic: license-file
|
|
248
250
|
|
|
249
251
|
# mantelo: A Keycloak Admin REST Api Client for Python
|
|
250
252
|
|
|
@@ -260,10 +262,10 @@ Requires-Dist: sphinxext-opengraph; extra == "docs"
|
|
|
260
262
|
|
|
261
263
|
> Mantelo [manˈtelo], from German "*Mantel*", from Late Latin "*mantum*" means "*cloak*" in Esperanto.
|
|
262
264
|
|
|
263
|
-
It stays fresh and complete because it does not hard-code or wrap any endpoint. Instead,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
It stays fresh and complete because it does not hard-code or wrap any endpoint. Instead, it offers
|
|
266
|
+
a clean, object-oriented interface to the Keycloak RESTful API. Acting as a lightweight wrapper
|
|
267
|
+
around the popular [requests](https://requests.readthedocs.io/en/latest/) library, mantelo takes
|
|
268
|
+
care of all the boring details for you - like authentication (tokens and refresh tokens), URL
|
|
267
269
|
management, serialization, and request processing
|
|
268
270
|
|
|
269
271
|
Any endpoint your Keycloak supports, mantelo supports!
|
|
@@ -312,6 +314,8 @@ To get started, install the package:
|
|
|
312
314
|
pip install mantelo
|
|
313
315
|
```
|
|
314
316
|
|
|
317
|
+
CI tests mantelo against Keycloak 26.0 and the latest release. Older versions may work, but are not tested.
|
|
318
|
+
|
|
315
319
|
Now, assuming you have a Keycloak Server running, what's left is to:
|
|
316
320
|
|
|
317
321
|
1. authenticate, see [🔐 Authenticate to Keycloak](#-authenticate-to-keycloak)
|
|
@@ -407,7 +411,8 @@ client = KeycloakAdmin.from_username_password(
|
|
|
407
411
|
> - to have "Client authentication" enabled,
|
|
408
412
|
> - to support the `Service accounts roles` authentication flow,
|
|
409
413
|
>
|
|
410
|
-
> - to have one or more service account roles granting access to Admin endpoints
|
|
414
|
+
> - to have one or more service account roles granting access to Admin endpoints,
|
|
415
|
+
> - since Keycloak 26.8.2, to have the `kc-admin-api` client scope as a default scope.
|
|
411
416
|
>
|
|
412
417
|
> Go to your client's "Credentials" tab to find the client secret.
|
|
413
418
|
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
> Mantelo [manˈtelo], from German "*Mantel*", from Late Latin "*mantum*" means "*cloak*" in Esperanto.
|
|
14
14
|
|
|
15
|
-
It stays fresh and complete because it does not hard-code or wrap any endpoint. Instead,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
It stays fresh and complete because it does not hard-code or wrap any endpoint. Instead, it offers
|
|
16
|
+
a clean, object-oriented interface to the Keycloak RESTful API. Acting as a lightweight wrapper
|
|
17
|
+
around the popular [requests](https://requests.readthedocs.io/en/latest/) library, mantelo takes
|
|
18
|
+
care of all the boring details for you - like authentication (tokens and refresh tokens), URL
|
|
19
19
|
management, serialization, and request processing
|
|
20
20
|
|
|
21
21
|
Any endpoint your Keycloak supports, mantelo supports!
|
|
@@ -64,6 +64,8 @@ To get started, install the package:
|
|
|
64
64
|
pip install mantelo
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
CI tests mantelo against Keycloak 26.0 and the latest release. Older versions may work, but are not tested.
|
|
68
|
+
|
|
67
69
|
Now, assuming you have a Keycloak Server running, what's left is to:
|
|
68
70
|
|
|
69
71
|
1. authenticate, see [🔐 Authenticate to Keycloak](#-authenticate-to-keycloak)
|
|
@@ -159,7 +161,8 @@ client = KeycloakAdmin.from_username_password(
|
|
|
159
161
|
> - to have "Client authentication" enabled,
|
|
160
162
|
> - to support the `Service accounts roles` authentication flow,
|
|
161
163
|
>
|
|
162
|
-
> - to have one or more service account roles granting access to Admin endpoints
|
|
164
|
+
> - to have one or more service account roles granting access to Admin endpoints,
|
|
165
|
+
> - since Keycloak 26.8.2, to have the `kc-admin-api` client scope as a default scope.
|
|
163
166
|
>
|
|
164
167
|
> Go to your client's "Credentials" tab to find the client secret.
|
|
165
168
|
|
|
@@ -15,6 +15,8 @@ To get started, install the package:
|
|
|
15
15
|
|
|
16
16
|
pip install mantelo
|
|
17
17
|
|
|
18
|
+
CI tests mantelo against Keycloak 26.0 and the latest release. Older versions may work, but are not tested.
|
|
19
|
+
|
|
18
20
|
Now, assuming you have a Keycloak Server running, what's left is to:
|
|
19
21
|
|
|
20
22
|
1. authenticate to Keycloak, :ref:`authentication`
|
|
@@ -11,8 +11,8 @@ Mantelo: A Keycloak Admin REST Api Client for Python
|
|
|
11
11
|
Mantelo [manˈtelo], from German "*Mantel*", from Late Latin "*mantum*" means "*cloak*" in Esperanto.
|
|
12
12
|
|
|
13
13
|
It always stays **fresh** and **complete** because it does not hard-code or wrap any endpoint.
|
|
14
|
-
Instead,
|
|
15
|
-
|
|
14
|
+
Instead, it offers a clean, object-oriented interface to the Keycloak RESTful API. Acting as a
|
|
15
|
+
lightweight wrapper around the popular `requests <https://requests.readthedocs.io/en/latest/>`_
|
|
16
16
|
library, mantelo takes care of all the boring details for you - like authentication (tokens and
|
|
17
17
|
refresh tokens), URL management, serialization, and request processing [#mention]_.
|
|
18
18
|
|
|
@@ -116,11 +116,8 @@ class Token:
|
|
|
116
116
|
|
|
117
117
|
:return: True if a refresh token exists and is still valid.
|
|
118
118
|
"""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
):
|
|
122
|
-
return True
|
|
123
|
-
return False
|
|
119
|
+
exp = self.refresh_expires_at
|
|
120
|
+
return bool(exp and _now() < (exp - timedelta(seconds=2)))
|
|
124
121
|
|
|
125
122
|
@classmethod
|
|
126
123
|
def from_dict(cls, data: dict, now: datetime | None = None) -> "Token":
|
|
@@ -214,6 +211,15 @@ class OpenidConnection(Connection, ABC):
|
|
|
214
211
|
resp = self.session.post(self.auth_url, data=data, auth=_NO_AUTH)
|
|
215
212
|
if resp.status_code == 401:
|
|
216
213
|
raise AuthenticationException(**resp.json(), response=resp)
|
|
214
|
+
if resp.status_code == 400:
|
|
215
|
+
# In Keycloak 26.6.0, invalid credentials returns 400
|
|
216
|
+
error = resp.json()
|
|
217
|
+
if (
|
|
218
|
+
error.get("error_description", "")
|
|
219
|
+
== "Invalid user credentials"
|
|
220
|
+
):
|
|
221
|
+
raise AuthenticationException(**error, response=resp)
|
|
222
|
+
|
|
217
223
|
resp.raise_for_status()
|
|
218
224
|
self._token = Token.from_dict(resp.json(), now=now)
|
|
219
225
|
_logger.debug(
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
|
|
1
3
|
import requests
|
|
2
4
|
from attrs import field, frozen
|
|
3
5
|
|
|
@@ -41,11 +43,9 @@ class HttpException(ManteloException):
|
|
|
41
43
|
@classmethod
|
|
42
44
|
def from_response(cls, response: requests.Response) -> "HttpException":
|
|
43
45
|
json = {}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
# Most API use JSON, but not all!
|
|
47
|
+
with contextlib.suppress(Exception):
|
|
46
48
|
json = response.json()
|
|
47
|
-
except Exception: # noqa: S110
|
|
48
|
-
pass
|
|
49
49
|
|
|
50
50
|
return cls(
|
|
51
51
|
url=response.request.url or "",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '2.2.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 2, 2)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'g40c5e5f7a'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: mantelo
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: ✨ Keycloak Admin REST Api Client for Python ✨
|
|
5
5
|
Author-email: Lucy Linder <lucy.derlin@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -230,6 +230,7 @@ Requires-Dist: attrs
|
|
|
230
230
|
Requires-Dist: requests
|
|
231
231
|
Provides-Extra: dev
|
|
232
232
|
Requires-Dist: build; extra == "dev"
|
|
233
|
+
Requires-Dist: ruff; extra == "dev"
|
|
233
234
|
Requires-Dist: coverage; extra == "dev"
|
|
234
235
|
Requires-Dist: tox; extra == "dev"
|
|
235
236
|
Requires-Dist: mypy; extra == "dev"
|
|
@@ -245,6 +246,7 @@ Requires-Dist: sphinx-book-theme; extra == "docs"
|
|
|
245
246
|
Requires-Dist: sphinx-autoapi; extra == "docs"
|
|
246
247
|
Requires-Dist: sphinx-copybutton; extra == "docs"
|
|
247
248
|
Requires-Dist: sphinxext-opengraph; extra == "docs"
|
|
249
|
+
Dynamic: license-file
|
|
248
250
|
|
|
249
251
|
# mantelo: A Keycloak Admin REST Api Client for Python
|
|
250
252
|
|
|
@@ -260,10 +262,10 @@ Requires-Dist: sphinxext-opengraph; extra == "docs"
|
|
|
260
262
|
|
|
261
263
|
> Mantelo [manˈtelo], from German "*Mantel*", from Late Latin "*mantum*" means "*cloak*" in Esperanto.
|
|
262
264
|
|
|
263
|
-
It stays fresh and complete because it does not hard-code or wrap any endpoint. Instead,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
It stays fresh and complete because it does not hard-code or wrap any endpoint. Instead, it offers
|
|
266
|
+
a clean, object-oriented interface to the Keycloak RESTful API. Acting as a lightweight wrapper
|
|
267
|
+
around the popular [requests](https://requests.readthedocs.io/en/latest/) library, mantelo takes
|
|
268
|
+
care of all the boring details for you - like authentication (tokens and refresh tokens), URL
|
|
267
269
|
management, serialization, and request processing
|
|
268
270
|
|
|
269
271
|
Any endpoint your Keycloak supports, mantelo supports!
|
|
@@ -312,6 +314,8 @@ To get started, install the package:
|
|
|
312
314
|
pip install mantelo
|
|
313
315
|
```
|
|
314
316
|
|
|
317
|
+
CI tests mantelo against Keycloak 26.0 and the latest release. Older versions may work, but are not tested.
|
|
318
|
+
|
|
315
319
|
Now, assuming you have a Keycloak Server running, what's left is to:
|
|
316
320
|
|
|
317
321
|
1. authenticate, see [🔐 Authenticate to Keycloak](#-authenticate-to-keycloak)
|
|
@@ -407,7 +411,8 @@ client = KeycloakAdmin.from_username_password(
|
|
|
407
411
|
> - to have "Client authentication" enabled,
|
|
408
412
|
> - to support the `Service accounts roles` authentication flow,
|
|
409
413
|
>
|
|
410
|
-
> - to have one or more service account roles granting access to Admin endpoints
|
|
414
|
+
> - to have one or more service account roles granting access to Admin endpoints,
|
|
415
|
+
> - since Keycloak 26.8.2, to have the `kc-admin-api` client scope as a default scope.
|
|
411
416
|
>
|
|
412
417
|
> Go to your client's "Credentials" tab to find the client secret.
|
|
413
418
|
|
|
@@ -41,6 +41,8 @@ mantelo.egg-info/PKG-INFO
|
|
|
41
41
|
mantelo.egg-info/SOURCES.txt
|
|
42
42
|
mantelo.egg-info/dependency_links.txt
|
|
43
43
|
mantelo.egg-info/requires.txt
|
|
44
|
+
mantelo.egg-info/scm_file_list.json
|
|
45
|
+
mantelo.egg-info/scm_version.json
|
|
44
46
|
mantelo.egg-info/top_level.txt
|
|
45
47
|
mantelo/internal/__init__.py
|
|
46
48
|
mantelo/internal/api.py
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
".github/workflows/build.yml",
|
|
4
|
+
".github/workflows/release-please-config.json",
|
|
5
|
+
".github/workflows/release.yml",
|
|
6
|
+
".github/workflows/weekly.yml",
|
|
7
|
+
".gitignore",
|
|
8
|
+
".readthedocs.yaml",
|
|
9
|
+
".release-please-manifest.json",
|
|
10
|
+
"CHANGELOG.md",
|
|
11
|
+
"CODEOWNERS",
|
|
12
|
+
"CONTRIBUTING.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"MANIFEST.in",
|
|
15
|
+
"Makefile",
|
|
16
|
+
"README.md",
|
|
17
|
+
"docker-compose.yml",
|
|
18
|
+
"docs/00-getting_started.rst",
|
|
19
|
+
"docs/01-authentication.rst",
|
|
20
|
+
"docs/02-making-calls.rst",
|
|
21
|
+
"docs/03-examples.rst",
|
|
22
|
+
"docs/04-faq.rst",
|
|
23
|
+
"docs/90-api.rst",
|
|
24
|
+
"docs/Makefile",
|
|
25
|
+
"docs/_static/css/custom.css",
|
|
26
|
+
"docs/_static/images/mantelo-128.png",
|
|
27
|
+
"docs/_static/images/mantelo-256.png",
|
|
28
|
+
"docs/_static/images/mantelo-512.png",
|
|
29
|
+
"docs/_static/images/mantelo-social-preview.png",
|
|
30
|
+
"docs/_static/images/mantelo-text-900.png",
|
|
31
|
+
"docs/_static/images/mantelo.ico",
|
|
32
|
+
"docs/_static/images/mantelo.svg",
|
|
33
|
+
"docs/_templates/.gitkeep",
|
|
34
|
+
"docs/_templates/layout.html",
|
|
35
|
+
"docs/conf.py",
|
|
36
|
+
"docs/index.rst",
|
|
37
|
+
"docs/make.bat",
|
|
38
|
+
"mantelo/__init__.py",
|
|
39
|
+
"mantelo/client.py",
|
|
40
|
+
"mantelo/connection.py",
|
|
41
|
+
"mantelo/exceptions.py",
|
|
42
|
+
"mantelo/internal/__init__.py",
|
|
43
|
+
"mantelo/internal/api.py",
|
|
44
|
+
"mantelo/internal/serializers.py",
|
|
45
|
+
"mantelo/py.typed",
|
|
46
|
+
"pyproject.toml",
|
|
47
|
+
"tests/__init__.py",
|
|
48
|
+
"tests/conftest.py",
|
|
49
|
+
"tests/constants.py",
|
|
50
|
+
"tests/internal/test_api.py",
|
|
51
|
+
"tests/internal/test_serialiers.py",
|
|
52
|
+
"tests/realms/master-realm.json",
|
|
53
|
+
"tests/realms/orwell-realm.json",
|
|
54
|
+
"tests/test_client.py",
|
|
55
|
+
"tests/test_connection.py",
|
|
56
|
+
"tox.ini"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -47,6 +47,7 @@ dependencies = [
|
|
|
47
47
|
[project.optional-dependencies]
|
|
48
48
|
dev = [
|
|
49
49
|
"build",
|
|
50
|
+
"ruff",
|
|
50
51
|
"coverage",
|
|
51
52
|
"tox",
|
|
52
53
|
"mypy",
|
|
@@ -98,14 +99,61 @@ write_to = "mantelo/version.py"
|
|
|
98
99
|
|
|
99
100
|
|
|
100
101
|
[tool.ruff]
|
|
101
|
-
|
|
102
|
-
target-version = "py310"
|
|
102
|
+
line-length = 79
|
|
103
|
+
target-version = "py310" # lowest supported version
|
|
103
104
|
|
|
104
105
|
[tool.ruff.lint]
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
# S101 (assert used) is deliberately not selected: we allow asserts.
|
|
107
|
+
select = [
|
|
108
|
+
"F", # Pyflakes
|
|
109
|
+
"E", # pycodestyle errors
|
|
110
|
+
"W", # pycodestyle warnings
|
|
111
|
+
"I", # isort
|
|
112
|
+
"UP", # pyupgrade
|
|
113
|
+
"YTT", # flake8-2020 (usage of sys package)
|
|
114
|
+
"S110", # try-except-pass detected, consider logging the exception
|
|
115
|
+
"B", # flake8-bugbear
|
|
116
|
+
"C4", # flake8-comprehensions
|
|
117
|
+
"T10", # flake8-debugger
|
|
118
|
+
"EXE", # flake8-executable
|
|
119
|
+
"FA", # flake8-future-annotations
|
|
120
|
+
"ISC", # flake8-implicit-str-concat
|
|
121
|
+
"PIE", # flake8-pie (misc lints)
|
|
122
|
+
"T20", # flake8-print
|
|
123
|
+
"PT", # flake8-pytest-style
|
|
124
|
+
"RSE", # flake8-raise
|
|
125
|
+
"RET", # flake8-return
|
|
126
|
+
"SIM", # flake8-simplify
|
|
127
|
+
"DTZ", # flake8-datetimez (token expiry relies on aware datetimes)
|
|
128
|
+
"LOG", # flake8-logging
|
|
129
|
+
"G", # flake8-logging-format
|
|
130
|
+
"PERF", # perflint
|
|
131
|
+
"PLE", # pylint ("E" subset)
|
|
132
|
+
"PLW", # pylint ("W" subset)
|
|
133
|
+
"FLY", # static join to f-string
|
|
134
|
+
"RUF", # ruff miscellaneous lints
|
|
107
135
|
]
|
|
108
136
|
|
|
137
|
+
ignore = [
|
|
138
|
+
"E501", # line too long (the formatter owns line length)
|
|
139
|
+
"B904", # raise-without-from-inside-except
|
|
140
|
+
"RET505", # superfluous else return
|
|
141
|
+
"PLW2901", # redefined loop name
|
|
142
|
+
"RUF012", # mutable class default
|
|
143
|
+
# Tests assert on the exception message via `excinfo.value`, which is
|
|
144
|
+
# stricter than `match=`, so requiring `match=` would be a step back.
|
|
145
|
+
"PT011", # pytest.raises too broad
|
|
146
|
+
|
|
147
|
+
# conflicts with the ruff formatter
|
|
148
|
+
"W191",
|
|
149
|
+
"ISC001",
|
|
150
|
+
"ISC002",
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
[tool.ruff.lint.isort]
|
|
154
|
+
lines-after-imports = 2
|
|
155
|
+
lines-between-types = 0
|
|
156
|
+
|
|
109
157
|
|
|
110
158
|
[tool.coverage.report]
|
|
111
159
|
# Regexes for lines to exclude from consideration
|
|
@@ -2,17 +2,16 @@ from unittest.mock import MagicMock
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from mantelo.internal import serializers
|
|
6
5
|
from mantelo.connection import (
|
|
7
6
|
ClientCredentialsConnection,
|
|
8
7
|
UsernamePasswordConnection,
|
|
9
8
|
)
|
|
10
|
-
from mantelo.internal import api
|
|
9
|
+
from mantelo.internal import api, serializers
|
|
11
10
|
|
|
12
11
|
from . import constants
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
@pytest.fixture
|
|
14
|
+
@pytest.fixture
|
|
16
15
|
def openid_connection_password():
|
|
17
16
|
return UsernamePasswordConnection(
|
|
18
17
|
server_url=constants.TEST_SERVER_URL,
|
|
@@ -23,7 +22,7 @@ def openid_connection_password():
|
|
|
23
22
|
)
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
@pytest.fixture
|
|
25
|
+
@pytest.fixture
|
|
27
26
|
def openid_connection_sa():
|
|
28
27
|
return ClientCredentialsConnection(
|
|
29
28
|
server_url=constants.TEST_SERVER_URL,
|
|
@@ -33,7 +32,7 @@ def openid_connection_sa():
|
|
|
33
32
|
)
|
|
34
33
|
|
|
35
34
|
|
|
36
|
-
@pytest.fixture
|
|
35
|
+
@pytest.fixture
|
|
37
36
|
def openid_connection_admin():
|
|
38
37
|
return UsernamePasswordConnection(
|
|
39
38
|
server_url=constants.TEST_SERVER_URL,
|
|
@@ -44,7 +43,7 @@ def openid_connection_admin():
|
|
|
44
43
|
)
|
|
45
44
|
|
|
46
45
|
|
|
47
|
-
@pytest.fixture
|
|
46
|
+
@pytest.fixture
|
|
48
47
|
def mock_session():
|
|
49
48
|
session = MagicMock()
|
|
50
49
|
session.request.return_value = MagicMock(status_code=200)
|
|
@@ -55,7 +54,7 @@ def mock_session():
|
|
|
55
54
|
return session
|
|
56
55
|
|
|
57
56
|
|
|
58
|
-
@pytest.fixture
|
|
57
|
+
@pytest.fixture
|
|
59
58
|
def mock_store(mock_session):
|
|
60
59
|
return api.Store(
|
|
61
60
|
base_url="https://example.com",
|
|
@@ -574,6 +574,28 @@
|
|
|
574
574
|
]
|
|
575
575
|
},
|
|
576
576
|
"clientScopes": [
|
|
577
|
+
{
|
|
578
|
+
"attributes": {
|
|
579
|
+
"display.on.consent.screen": "false",
|
|
580
|
+
"include.in.token.scope": "true"
|
|
581
|
+
},
|
|
582
|
+
"description": "Grants access to the Keycloak Admin REST API by adding its required audience to the token",
|
|
583
|
+
"name": "kc-admin-api",
|
|
584
|
+
"protocol": "openid-connect",
|
|
585
|
+
"protocolMappers": [
|
|
586
|
+
{
|
|
587
|
+
"config": {
|
|
588
|
+
"access.token.claim": "true",
|
|
589
|
+
"introspection.token.claim": "true",
|
|
590
|
+
"lightweight.claim": "true"
|
|
591
|
+
},
|
|
592
|
+
"consentRequired": false,
|
|
593
|
+
"name": "admin-api-audience",
|
|
594
|
+
"protocol": "openid-connect",
|
|
595
|
+
"protocolMapper": "oidc-admin-api-audience-mapper"
|
|
596
|
+
}
|
|
597
|
+
]
|
|
598
|
+
},
|
|
577
599
|
{
|
|
578
600
|
"attributes": {
|
|
579
601
|
"consent.screen.text": "${offlineAccessScopeConsentText}",
|
|
@@ -1375,6 +1397,7 @@
|
|
|
1375
1397
|
"defaultClientScopes": [
|
|
1376
1398
|
"web-origins",
|
|
1377
1399
|
"acr",
|
|
1400
|
+
"kc-admin-api",
|
|
1378
1401
|
"profile",
|
|
1379
1402
|
"roles",
|
|
1380
1403
|
"email"
|
|
@@ -118,7 +118,7 @@ def test_switch_realm(openid_connection_admin):
|
|
|
118
118
|
(403, lambda c: c.clients.get()),
|
|
119
119
|
(400, lambda c: c.users.post({"foo": "bar"})),
|
|
120
120
|
(400, lambda c: c.users.post({})),
|
|
121
|
-
(405, lambda c: c.groups.
|
|
121
|
+
(405, lambda c: c.groups.patch()),
|
|
122
122
|
(405, lambda c: c.users.put({})),
|
|
123
123
|
(404, lambda c: c.users("not-exist").get()),
|
|
124
124
|
(
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
2
|
|
|
3
|
+
import pytest
|
|
4
|
+
import requests
|
|
3
5
|
from attrs import evolve
|
|
6
|
+
|
|
4
7
|
from mantelo.connection import (
|
|
5
|
-
Token,
|
|
6
8
|
AuthenticationException,
|
|
7
9
|
ClientCredentialsConnection,
|
|
10
|
+
Token,
|
|
8
11
|
UsernamePasswordConnection,
|
|
9
12
|
)
|
|
10
|
-
from datetime import datetime, timedelta, timezone
|
|
11
|
-
import requests
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def test_token_from_dict():
|
|
@@ -35,7 +36,7 @@ def test_token_from_dict():
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def test_refresh_without_expire():
|
|
38
|
-
with pytest.raises(TypeError, match="Missing refresh_expires_in
|
|
39
|
+
with pytest.raises(TypeError, match=r"Missing refresh_expires_in\."):
|
|
39
40
|
Token(
|
|
40
41
|
access_token="<any>",
|
|
41
42
|
expires_in=123,
|
|
@@ -145,12 +146,12 @@ def test_ClientCredentialsconnection_init():
|
|
|
145
146
|
|
|
146
147
|
|
|
147
148
|
def test_openidconnection_default_values():
|
|
148
|
-
args =
|
|
149
|
-
server_url
|
|
150
|
-
realm_name
|
|
151
|
-
client_id
|
|
152
|
-
client_secret
|
|
153
|
-
|
|
149
|
+
args = {
|
|
150
|
+
"server_url": "https://kc.test",
|
|
151
|
+
"realm_name": "test",
|
|
152
|
+
"client_id": "foo-client",
|
|
153
|
+
"client_secret": "s3cr3t",
|
|
154
|
+
}
|
|
154
155
|
|
|
155
156
|
# Defaults
|
|
156
157
|
conn = ClientCredentialsConnection(**args)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{".":"2.2.0"}
|
mantelo-2.2.0/mantelo/version.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools_scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
TYPE_CHECKING = False
|
|
4
|
-
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple, Union
|
|
6
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
-
else:
|
|
8
|
-
VERSION_TUPLE = object
|
|
9
|
-
|
|
10
|
-
version: str
|
|
11
|
-
__version__: str
|
|
12
|
-
__version_tuple__: VERSION_TUPLE
|
|
13
|
-
version_tuple: VERSION_TUPLE
|
|
14
|
-
|
|
15
|
-
__version__ = version = '2.2.0'
|
|
16
|
-
__version_tuple__ = version_tuple = (2, 2, 0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -3,8 +3,8 @@ from unittest.mock import MagicMock, Mock, PropertyMock
|
|
|
3
3
|
import pytest
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
|
-
from mantelo.internal import api as _api
|
|
7
6
|
from mantelo import exceptions
|
|
7
|
+
from mantelo.internal import api as _api
|
|
8
8
|
from mantelo.internal.serializers import JsonSerializer
|
|
9
9
|
|
|
10
10
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|