cardo-python-utils 0.5.dev24__tar.gz → 0.5.dev26__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.
- {cardo_python_utils-0.5.dev24/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev26}/PKG-INFO +1 -1
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26/cardo_python_utils.egg-info}/PKG-INFO +1 -1
- cardo_python_utils-0.5.dev26/cardo_python_utils.egg-info/SOURCES.txt +35 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/pyproject.toml +1 -1
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/admin/auth.py +11 -3
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/service.py +24 -42
- cardo_python_utils-0.5.dev26/python_utils/keycloak/utils.py +51 -0
- cardo_python_utils-0.5.dev24/cardo_python_utils.egg-info/SOURCES.txt +0 -35
- cardo_python_utils-0.5.dev24/python_utils/django/keycloak/admin/user_groups_changelist.html +0 -7
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/LICENSE +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/MANIFEST.in +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/README.rst +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/cardo_python_utils.egg-info/requires.txt +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/cardo_python_utils.egg-info/top_level.txt +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/__init__.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/choices.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/data_structures.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/db.py +0 -0
- /cardo_python_utils-0.5.dev24/python_utils/django/utils.py → /cardo_python_utils-0.5.dev26/python_utils/django_utils.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/esma_choices.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/exceptions.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/imports.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/__init__.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/admin/__init__.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/admin/user_group.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/api/__init__.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/api/drf.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/api/ninja.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/api/utils.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/models/__init__.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/models/user_group.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/tests/__init__.py +0 -0
- {cardo_python_utils-0.5.dev24/python_utils/django/keycloak → cardo_python_utils-0.5.dev26/python_utils/keycloak/django}/tests/conftest.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/math.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/text.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/time.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/types_hinting.py +0 -0
- {cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/setup.cfg +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.rst
|
|
4
|
+
pyproject.toml
|
|
5
|
+
cardo_python_utils.egg-info/PKG-INFO
|
|
6
|
+
cardo_python_utils.egg-info/SOURCES.txt
|
|
7
|
+
cardo_python_utils.egg-info/dependency_links.txt
|
|
8
|
+
cardo_python_utils.egg-info/requires.txt
|
|
9
|
+
cardo_python_utils.egg-info/top_level.txt
|
|
10
|
+
python_utils/__init__.py
|
|
11
|
+
python_utils/choices.py
|
|
12
|
+
python_utils/data_structures.py
|
|
13
|
+
python_utils/db.py
|
|
14
|
+
python_utils/django_utils.py
|
|
15
|
+
python_utils/esma_choices.py
|
|
16
|
+
python_utils/exceptions.py
|
|
17
|
+
python_utils/imports.py
|
|
18
|
+
python_utils/math.py
|
|
19
|
+
python_utils/text.py
|
|
20
|
+
python_utils/time.py
|
|
21
|
+
python_utils/types_hinting.py
|
|
22
|
+
python_utils/keycloak/utils.py
|
|
23
|
+
python_utils/keycloak/django/__init__.py
|
|
24
|
+
python_utils/keycloak/django/service.py
|
|
25
|
+
python_utils/keycloak/django/admin/__init__.py
|
|
26
|
+
python_utils/keycloak/django/admin/auth.py
|
|
27
|
+
python_utils/keycloak/django/admin/user_group.py
|
|
28
|
+
python_utils/keycloak/django/api/__init__.py
|
|
29
|
+
python_utils/keycloak/django/api/drf.py
|
|
30
|
+
python_utils/keycloak/django/api/ninja.py
|
|
31
|
+
python_utils/keycloak/django/api/utils.py
|
|
32
|
+
python_utils/keycloak/django/models/__init__.py
|
|
33
|
+
python_utils/keycloak/django/models/user_group.py
|
|
34
|
+
python_utils/keycloak/django/tests/__init__.py
|
|
35
|
+
python_utils/keycloak/django/tests/conftest.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cardo-python-utils"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.dev26"
|
|
8
8
|
description = "Python library enhanced with a wide range of functions for different scenarios."
|
|
9
9
|
readme = "README.rst"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
2
|
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
|
|
3
3
|
|
|
4
|
+
from ...utils import get_keycloak_confidential_client_token
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class AdminAuthenticationBackend(OIDCAuthenticationBackend):
|
|
8
|
+
def get_token(self, payload):
|
|
9
|
+
# Instead of passing client_id and client_secret,
|
|
10
|
+
# client_assertion with service account token will be used
|
|
11
|
+
payload.pop("client_id", None)
|
|
12
|
+
payload.pop("client_secret", None)
|
|
13
|
+
|
|
14
|
+
return get_keycloak_confidential_client_token(**payload)
|
|
15
|
+
|
|
6
16
|
def _get_user_data(self, claims) -> dict:
|
|
7
17
|
client_roles = (
|
|
8
|
-
claims.get("resource_access", {})
|
|
9
|
-
.get(getattr(settings, "OIDC_RP_CLIENT_ID", ""), {})
|
|
10
|
-
.get("roles", [])
|
|
18
|
+
claims.get("resource_access", {}).get(getattr(settings, "OIDC_RP_CLIENT_ID", ""), {}).get("roles", [])
|
|
11
19
|
)
|
|
12
20
|
is_superuser = "Admin" in client_roles
|
|
13
21
|
|
|
@@ -5,6 +5,13 @@ from keycloak import KeycloakAdmin
|
|
|
5
5
|
from keycloak import KeycloakOpenIDConnection
|
|
6
6
|
from keycloak.exceptions import KeycloakGetError
|
|
7
7
|
|
|
8
|
+
from ..utils import (
|
|
9
|
+
KEYCLOAK_SERVER_URL,
|
|
10
|
+
KEYCLOAK_REALM,
|
|
11
|
+
KEYCLOAK_CONFIDENTIAL_CLIENT_ID,
|
|
12
|
+
get_keycloak_confidential_client_token,
|
|
13
|
+
)
|
|
14
|
+
|
|
8
15
|
|
|
9
16
|
def get_user_group_model():
|
|
10
17
|
"""
|
|
@@ -21,10 +28,7 @@ def get_user_group_model():
|
|
|
21
28
|
try:
|
|
22
29
|
model_string = getattr(settings, "KEYCLOAK_USER_GROUP_MODEL")
|
|
23
30
|
except AttributeError:
|
|
24
|
-
raise LookupError(
|
|
25
|
-
"Please set KEYCLOAK_USER_GROUP_MODEL in your Django settings "
|
|
26
|
-
"(e.g., 'myapp.UserGroup')."
|
|
27
|
-
)
|
|
31
|
+
raise LookupError("Please set KEYCLOAK_USER_GROUP_MODEL in your Django settings (e.g., 'myapp.UserGroup').")
|
|
28
32
|
|
|
29
33
|
return apps.get_model(model_string)
|
|
30
34
|
|
|
@@ -52,14 +56,10 @@ class KeycloakService:
|
|
|
52
56
|
|
|
53
57
|
reported_group_ids = set()
|
|
54
58
|
for group in groups:
|
|
55
|
-
self._process_group_recursively(
|
|
56
|
-
group, existing_groups_by_id, reported_group_ids
|
|
57
|
-
)
|
|
59
|
+
self._process_group_recursively(group, existing_groups_by_id, reported_group_ids)
|
|
58
60
|
|
|
59
61
|
# Identify deleted groups
|
|
60
|
-
deleted_groups = self._user_group_model.objects.exclude(
|
|
61
|
-
id__in=reported_group_ids
|
|
62
|
-
)
|
|
62
|
+
deleted_groups = self._user_group_model.objects.exclude(id__in=reported_group_ids)
|
|
63
63
|
if deleted_groups.exists():
|
|
64
64
|
print(
|
|
65
65
|
f"Deleting groups no longer present in Keycloak: {list(deleted_groups.values_list('path', flat=True))}"
|
|
@@ -68,11 +68,11 @@ class KeycloakService:
|
|
|
68
68
|
|
|
69
69
|
def _get_keycloak_admin(self):
|
|
70
70
|
keycloak_connection = KeycloakOpenIDConnection(
|
|
71
|
-
server_url=
|
|
72
|
-
realm_name=
|
|
73
|
-
user_realm_name=
|
|
74
|
-
client_id=
|
|
75
|
-
|
|
71
|
+
server_url=KEYCLOAK_SERVER_URL,
|
|
72
|
+
realm_name=KEYCLOAK_REALM,
|
|
73
|
+
user_realm_name=KEYCLOAK_REALM,
|
|
74
|
+
client_id=KEYCLOAK_CONFIDENTIAL_CLIENT_ID,
|
|
75
|
+
token=get_keycloak_confidential_client_token(),
|
|
76
76
|
verify=True,
|
|
77
77
|
)
|
|
78
78
|
return KeycloakAdmin(connection=keycloak_connection)
|
|
@@ -86,9 +86,7 @@ class KeycloakService:
|
|
|
86
86
|
if group_id in existing_groups_by_id:
|
|
87
87
|
existing_group = existing_groups_by_id[group_id]
|
|
88
88
|
if existing_group.path != group["path"]:
|
|
89
|
-
print(
|
|
90
|
-
f"Updating group path from {existing_group.path} to {group['path']}..."
|
|
91
|
-
)
|
|
89
|
+
print(f"Updating group path from {existing_group.path} to {group['path']}...")
|
|
92
90
|
existing_group.path = group["path"]
|
|
93
91
|
existing_group.save()
|
|
94
92
|
else:
|
|
@@ -97,9 +95,7 @@ class KeycloakService:
|
|
|
97
95
|
|
|
98
96
|
if subgroups := group.get("subGroups"):
|
|
99
97
|
for subgroup in subgroups:
|
|
100
|
-
self._process_group_recursively(
|
|
101
|
-
subgroup, existing_groups_by_id, reported_group_ids
|
|
102
|
-
)
|
|
98
|
+
self._process_group_recursively(subgroup, existing_groups_by_id, reported_group_ids)
|
|
103
99
|
|
|
104
100
|
|
|
105
101
|
class KeycloakServiceAsync(KeycloakService):
|
|
@@ -121,24 +117,16 @@ class KeycloakServiceAsync(KeycloakService):
|
|
|
121
117
|
|
|
122
118
|
# Process existing and new groups
|
|
123
119
|
existing_groups = self._user_group_model.objects.all()
|
|
124
|
-
existing_groups_by_id = {
|
|
125
|
-
str(group.id): group async for group in existing_groups
|
|
126
|
-
}
|
|
120
|
+
existing_groups_by_id = {str(group.id): group async for group in existing_groups}
|
|
127
121
|
|
|
128
122
|
reported_group_ids = set()
|
|
129
123
|
for group in groups:
|
|
130
|
-
await self._process_group_recursively(
|
|
131
|
-
group, existing_groups_by_id, reported_group_ids
|
|
132
|
-
)
|
|
124
|
+
await self._process_group_recursively(group, existing_groups_by_id, reported_group_ids)
|
|
133
125
|
|
|
134
126
|
# Identify deleted groups
|
|
135
|
-
deleted_groups = self._user_group_model.objects.exclude(
|
|
136
|
-
id__in=reported_group_ids
|
|
137
|
-
)
|
|
127
|
+
deleted_groups = self._user_group_model.objects.exclude(id__in=reported_group_ids)
|
|
138
128
|
if await deleted_groups.aexists():
|
|
139
|
-
paths = [
|
|
140
|
-
path async for path in deleted_groups.values_list("path", flat=True)
|
|
141
|
-
]
|
|
129
|
+
paths = [path async for path in deleted_groups.values_list("path", flat=True)]
|
|
142
130
|
print(f"Deleting groups no longer present in Keycloak: {paths}")
|
|
143
131
|
|
|
144
132
|
await deleted_groups.adelete()
|
|
@@ -152,22 +140,16 @@ class KeycloakServiceAsync(KeycloakService):
|
|
|
152
140
|
if group_id in existing_groups_by_id:
|
|
153
141
|
existing_group = existing_groups_by_id[group_id]
|
|
154
142
|
if existing_group.path != group["path"]:
|
|
155
|
-
print(
|
|
156
|
-
f"Updating group path from {existing_group.path} to {group['path']}..."
|
|
157
|
-
)
|
|
143
|
+
print(f"Updating group path from {existing_group.path} to {group['path']}...")
|
|
158
144
|
existing_group.path = group["path"]
|
|
159
145
|
await existing_group.asave()
|
|
160
146
|
else:
|
|
161
147
|
print(f"Creating new group with path {group['path']}...")
|
|
162
|
-
await self._user_group_model.objects.acreate(
|
|
163
|
-
id=group_id, path=group["path"]
|
|
164
|
-
)
|
|
148
|
+
await self._user_group_model.objects.acreate(id=group_id, path=group["path"])
|
|
165
149
|
|
|
166
150
|
if subgroups := group.get("subGroups"):
|
|
167
151
|
for subgroup in subgroups:
|
|
168
|
-
await self._process_group_recursively(
|
|
169
|
-
subgroup, existing_groups_by_id, reported_group_ids
|
|
170
|
-
)
|
|
152
|
+
await self._process_group_recursively(subgroup, existing_groups_by_id, reported_group_ids)
|
|
171
153
|
|
|
172
154
|
|
|
173
155
|
class AuthServiceBase:
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
KEYCLOAK_SERVER_URL = os.getenv("KEYCLOAK_SERVER_URL")
|
|
5
|
+
KEYCLOAK_REALM = os.getenv("KEYCLOAK_REALM")
|
|
6
|
+
KEYCLOAK_REALM_URL = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}"
|
|
7
|
+
KEYCLOAK_TOKEN_ENDPOINT = f"{KEYCLOAK_REALM_URL}/protocol/openid-connect/token"
|
|
8
|
+
|
|
9
|
+
KEYCLOAK_CONFIDENTIAL_CLIENT_ID = os.getenv("KEYCLOAK_CONFIDENTIAL_CLIENT_ID")
|
|
10
|
+
KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATH = os.getenv(
|
|
11
|
+
"KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATH"
|
|
12
|
+
)
|
|
13
|
+
KEYCLOAK_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"
|
|
14
|
+
KEYCLOAK_CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_confidential_client_service_account_token() -> str:
|
|
18
|
+
"""
|
|
19
|
+
Reads the Keycloak confidential client service account token from a file.
|
|
20
|
+
"""
|
|
21
|
+
token_file_path = KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATH
|
|
22
|
+
if not token_file_path or not os.path.isfile(token_file_path):
|
|
23
|
+
raise FileNotFoundError(f"Keycloak service account token file not found: {token_file_path}")
|
|
24
|
+
|
|
25
|
+
with open(token_file_path, "r") as f:
|
|
26
|
+
token = f.read().strip()
|
|
27
|
+
|
|
28
|
+
if not token:
|
|
29
|
+
raise ValueError("Keycloak service account token is empty.")
|
|
30
|
+
|
|
31
|
+
return token
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_keycloak_confidential_client_token(**kwargs) -> dict:
|
|
35
|
+
"""
|
|
36
|
+
Obtains token for a Keycloak confidential client with the client credentials grant,
|
|
37
|
+
using a service account token for authentication.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
response = requests.post(
|
|
41
|
+
KEYCLOAK_TOKEN_ENDPOINT,
|
|
42
|
+
data={
|
|
43
|
+
"grant_type": KEYCLOAK_CLIENT_CREDENTIALS_GRANT_TYPE,
|
|
44
|
+
"client_assertion_type": KEYCLOAK_CLIENT_ASSERTION_TYPE,
|
|
45
|
+
"client_assertion": get_confidential_client_service_account_token(),
|
|
46
|
+
**kwargs,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
response.raise_for_status()
|
|
50
|
+
|
|
51
|
+
return response.json()
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
MANIFEST.in
|
|
3
|
-
README.rst
|
|
4
|
-
pyproject.toml
|
|
5
|
-
cardo_python_utils.egg-info/PKG-INFO
|
|
6
|
-
cardo_python_utils.egg-info/SOURCES.txt
|
|
7
|
-
cardo_python_utils.egg-info/dependency_links.txt
|
|
8
|
-
cardo_python_utils.egg-info/requires.txt
|
|
9
|
-
cardo_python_utils.egg-info/top_level.txt
|
|
10
|
-
python_utils/__init__.py
|
|
11
|
-
python_utils/choices.py
|
|
12
|
-
python_utils/data_structures.py
|
|
13
|
-
python_utils/db.py
|
|
14
|
-
python_utils/esma_choices.py
|
|
15
|
-
python_utils/exceptions.py
|
|
16
|
-
python_utils/imports.py
|
|
17
|
-
python_utils/math.py
|
|
18
|
-
python_utils/text.py
|
|
19
|
-
python_utils/time.py
|
|
20
|
-
python_utils/types_hinting.py
|
|
21
|
-
python_utils/django/utils.py
|
|
22
|
-
python_utils/django/keycloak/__init__.py
|
|
23
|
-
python_utils/django/keycloak/service.py
|
|
24
|
-
python_utils/django/keycloak/admin/__init__.py
|
|
25
|
-
python_utils/django/keycloak/admin/auth.py
|
|
26
|
-
python_utils/django/keycloak/admin/user_group.py
|
|
27
|
-
python_utils/django/keycloak/admin/user_groups_changelist.html
|
|
28
|
-
python_utils/django/keycloak/api/__init__.py
|
|
29
|
-
python_utils/django/keycloak/api/drf.py
|
|
30
|
-
python_utils/django/keycloak/api/ninja.py
|
|
31
|
-
python_utils/django/keycloak/api/utils.py
|
|
32
|
-
python_utils/django/keycloak/models/__init__.py
|
|
33
|
-
python_utils/django/keycloak/models/user_group.py
|
|
34
|
-
python_utils/django/keycloak/tests/__init__.py
|
|
35
|
-
python_utils/django/keycloak/tests/conftest.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev24 → cardo_python_utils-0.5.dev26}/python_utils/data_structures.py
RENAMED
|
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
|