cardo-python-utils 0.5.dev1__tar.gz → 0.5.dev2__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.dev1/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev2}/PKG-INFO +4 -1
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2/cardo_python_utils.egg-info}/PKG-INFO +4 -1
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/cardo_python_utils.egg-info/SOURCES.txt +1 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/cardo_python_utils.egg-info/requires.txt +4 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/pyproject.toml +5 -1
- cardo_python_utils-0.5.dev2/python_utils/django/auth/keycloak.py +119 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/LICENSE +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/MANIFEST.in +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/README.rst +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/cardo_python_utils.egg-info/top_level.txt +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/__init__.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/choices.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/data_structures.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/db.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/django/auth/__init__.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/django/auth/admin.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/django/auth/drf.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/django/auth/ninja.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/django/utils.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/esma_choices.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/exceptions.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/imports.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/math.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/text.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/time.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/types_hinting.py +0 -0
- {cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/setup.cfg +0 -0
{cardo_python_utils-0.5.dev1/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cardo-python-utils
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.dev2
|
|
4
4
|
Summary: Python library enhanced with a wide range of functions for different scenarios.
|
|
5
5
|
Author-email: CardoAI <hello@cardoai.com>
|
|
6
6
|
License: MIT
|
|
@@ -37,6 +37,9 @@ Requires-Dist: PyJWT; extra == "drf"
|
|
|
37
37
|
Provides-Extra: django-admin-auth
|
|
38
38
|
Requires-Dist: Django; extra == "django-admin-auth"
|
|
39
39
|
Requires-Dist: mozilla-django-oidc>=4.0.1; extra == "django-admin-auth"
|
|
40
|
+
Provides-Extra: django-keycloak-groups
|
|
41
|
+
Requires-Dist: Django; extra == "django-keycloak-groups"
|
|
42
|
+
Requires-Dist: python-keycloak>=5.8.1; extra == "django-keycloak-groups"
|
|
40
43
|
Provides-Extra: all
|
|
41
44
|
Requires-Dist: Django; extra == "all"
|
|
42
45
|
Provides-Extra: dev
|
{cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2/cardo_python_utils.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cardo-python-utils
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.dev2
|
|
4
4
|
Summary: Python library enhanced with a wide range of functions for different scenarios.
|
|
5
5
|
Author-email: CardoAI <hello@cardoai.com>
|
|
6
6
|
License: MIT
|
|
@@ -37,6 +37,9 @@ Requires-Dist: PyJWT; extra == "drf"
|
|
|
37
37
|
Provides-Extra: django-admin-auth
|
|
38
38
|
Requires-Dist: Django; extra == "django-admin-auth"
|
|
39
39
|
Requires-Dist: mozilla-django-oidc>=4.0.1; extra == "django-admin-auth"
|
|
40
|
+
Provides-Extra: django-keycloak-groups
|
|
41
|
+
Requires-Dist: Django; extra == "django-keycloak-groups"
|
|
42
|
+
Requires-Dist: python-keycloak>=5.8.1; extra == "django-keycloak-groups"
|
|
40
43
|
Provides-Extra: all
|
|
41
44
|
Requires-Dist: Django; extra == "all"
|
|
42
45
|
Provides-Extra: dev
|
|
@@ -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.dev2"
|
|
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"
|
|
@@ -48,6 +48,10 @@ django-admin-auth = [
|
|
|
48
48
|
"Django",
|
|
49
49
|
"mozilla-django-oidc>=4.0.1",
|
|
50
50
|
]
|
|
51
|
+
django-keycloak-groups = [
|
|
52
|
+
"Django",
|
|
53
|
+
"python-keycloak>=5.8.1",
|
|
54
|
+
]
|
|
51
55
|
all = [
|
|
52
56
|
"Django",
|
|
53
57
|
]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from django.apps import apps
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from django.db import models
|
|
4
|
+
from keycloak import KeycloakAdmin
|
|
5
|
+
from keycloak import KeycloakOpenIDConnection
|
|
6
|
+
from keycloak.exceptions import KeycloakGetError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UserGroupBase(models.Model):
|
|
10
|
+
"""
|
|
11
|
+
Abstract base model for Keycloak user groups.
|
|
12
|
+
"""
|
|
13
|
+
id = models.UUIDField(
|
|
14
|
+
primary_key=True,
|
|
15
|
+
help_text="The ID of the group, as coming from Keycloak.",
|
|
16
|
+
)
|
|
17
|
+
path = models.CharField(
|
|
18
|
+
max_length=255,
|
|
19
|
+
help_text="The full path of the group, as coming from Keycloak.",
|
|
20
|
+
db_index=True,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
class Meta:
|
|
24
|
+
abstract = True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class KeycloakService:
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self._user_group_model = self._get_user_group_model()
|
|
30
|
+
self._keycloak_admin = self._get_keycloak_admin()
|
|
31
|
+
|
|
32
|
+
def sync_user_groups(self, raise_exceptions: bool = False):
|
|
33
|
+
print("Syncing user groups from Keycloak...")
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
groups = self._keycloak_admin.get_groups(full_hierarchy=True)
|
|
37
|
+
except KeycloakGetError as e:
|
|
38
|
+
print(f"Failed to fetch groups from Keycloak: {str(e)}")
|
|
39
|
+
if raise_exceptions:
|
|
40
|
+
raise e
|
|
41
|
+
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Process existing and new groups
|
|
45
|
+
existing_groups = self._user_group_model.objects.all()
|
|
46
|
+
existing_groups_by_id = {str(group.id): group for group in existing_groups}
|
|
47
|
+
|
|
48
|
+
reported_group_ids = set()
|
|
49
|
+
for group in groups:
|
|
50
|
+
self._process_group_recursively(
|
|
51
|
+
group, existing_groups_by_id, reported_group_ids
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Identify deleted groups
|
|
55
|
+
deleted_groups = self._user_group_model.objects.exclude(
|
|
56
|
+
id__in=reported_group_ids
|
|
57
|
+
)
|
|
58
|
+
if deleted_groups.exists():
|
|
59
|
+
print(
|
|
60
|
+
f"Deleting groups no longer present in Keycloak: {list(deleted_groups.values_list('path', flat=True))}"
|
|
61
|
+
)
|
|
62
|
+
deleted_groups.delete()
|
|
63
|
+
|
|
64
|
+
def _get_keycloak_admin(self):
|
|
65
|
+
keycloak_connection = KeycloakOpenIDConnection(
|
|
66
|
+
server_url=settings.KEYCLOAK_SERVER_URL,
|
|
67
|
+
realm_name=settings.KEYCLOAK_REALM,
|
|
68
|
+
user_realm_name=settings.KEYCLOAK_REALM,
|
|
69
|
+
client_id=settings.KEYCLOAK_ADMIN_CLIENT_ID,
|
|
70
|
+
client_secret_key=settings.KEYCLOAK_ADMIN_CLIENT_SECRET,
|
|
71
|
+
verify=True,
|
|
72
|
+
)
|
|
73
|
+
return KeycloakAdmin(connection=keycloak_connection)
|
|
74
|
+
|
|
75
|
+
def _get_user_group_model(self):
|
|
76
|
+
"""
|
|
77
|
+
Dynamically get the UserGroup model.
|
|
78
|
+
|
|
79
|
+
Attempts to use the model specified in settings.KEYCLOAK_USER_GROUP_MODEL.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The UserGroup model class.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
LookupError: If the model cannot be found.
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
model_string = getattr(settings, "KEYCLOAK_USER_GROUP_MODEL")
|
|
89
|
+
except AttributeError:
|
|
90
|
+
raise LookupError(
|
|
91
|
+
"Please set KEYCLOAK_USER_GROUP_MODEL in your Django settings "
|
|
92
|
+
"(e.g., 'myapp.models.UserGroup')."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return apps.get_model(model_string)
|
|
96
|
+
|
|
97
|
+
def _process_group_recursively(
|
|
98
|
+
self, group, existing_groups_by_id, reported_group_ids
|
|
99
|
+
):
|
|
100
|
+
group_id = str(group["id"])
|
|
101
|
+
reported_group_ids.add(group_id)
|
|
102
|
+
|
|
103
|
+
if group_id in existing_groups_by_id:
|
|
104
|
+
existing_group = existing_groups_by_id[group_id]
|
|
105
|
+
if existing_group.path != group["path"]:
|
|
106
|
+
print(
|
|
107
|
+
f"Updating group path from {existing_group.path} to {group['path']}..."
|
|
108
|
+
)
|
|
109
|
+
existing_group.path = group["path"]
|
|
110
|
+
existing_group.save()
|
|
111
|
+
else:
|
|
112
|
+
print(f"Creating new group with path {group['path']}...")
|
|
113
|
+
self._user_group_model.objects.create(id=group_id, path=group["path"])
|
|
114
|
+
|
|
115
|
+
if subgroups := group.get("subGroups"):
|
|
116
|
+
for subgroup in subgroups:
|
|
117
|
+
self._process_group_recursively(
|
|
118
|
+
subgroup, existing_groups_by_id, reported_group_ids
|
|
119
|
+
)
|
|
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
|
{cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/django/auth/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/django/auth/admin.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev1 → cardo_python_utils-0.5.dev2}/python_utils/django/auth/ninja.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
|