cardo-python-utils 0.5.dev39__tar.gz → 0.5.dev40__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.dev39/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev40}/PKG-INFO +1 -1
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40/cardo_python_utils.egg-info}/PKG-INFO +1 -1
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/cardo_python_utils.egg-info/SOURCES.txt +1 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/pyproject.toml +1 -1
- cardo_python_utils-0.5.dev40/python_utils/django/middleware/tenant_aware_websocket_middleware.py +129 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/LICENSE +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/MANIFEST.in +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/README.rst +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/cardo_python_utils.egg-info/requires.txt +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/cardo_python_utils.egg-info/top_level.txt +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/choices.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/data_structures.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/db.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/README.md +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/auth.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/templates/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/templates/user_groups_changelist.html +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/user_group.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/views.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/api/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/api/drf.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/api/ninja.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/api/utils.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/apps.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/auth/service.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/celery/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/celery/tenant_aware_database_scheduler.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/celery/tenant_aware_task.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/db/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/db/routers.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/db/transaction.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/db/utils.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/management/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/management/commands/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/management/commands/migrateall.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/management/commands/tenant_aware_command.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/middleware/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/middleware/tenant_aware_http_middleware.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/models/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/models/user_group.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/oidc_settings.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/redis/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/redis/key_function.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/settings.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/storage/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/storage/tenant_aware_storage.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/tenant_context.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/tests/__init__.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/tests/conftest.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django_utils.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/esma_choices.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/exceptions.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/imports.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/math.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/text.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/time.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/types_hinting.py +0 -0
- {cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/setup.cfg +0 -0
|
@@ -49,6 +49,7 @@ python_utils/django/management/commands/migrateall.py
|
|
|
49
49
|
python_utils/django/management/commands/tenant_aware_command.py
|
|
50
50
|
python_utils/django/middleware/__init__.py
|
|
51
51
|
python_utils/django/middleware/tenant_aware_http_middleware.py
|
|
52
|
+
python_utils/django/middleware/tenant_aware_websocket_middleware.py
|
|
52
53
|
python_utils/django/models/__init__.py
|
|
53
54
|
python_utils/django/models/user_group.py
|
|
54
55
|
python_utils/django/redis/__init__.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.dev40"
|
|
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"
|
cardo_python_utils-0.5.dev40/python_utils/django/middleware/tenant_aware_websocket_middleware.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from urllib.parse import parse_qs
|
|
3
|
+
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.contrib.auth import get_user_model
|
|
6
|
+
|
|
7
|
+
from ..api.utils import decode_jwt, TokenPayload
|
|
8
|
+
from ..settings import DEVELOPMENT_TENANT
|
|
9
|
+
from ..tenant_context import TenantContext
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_query_params_from_scope(scope):
|
|
15
|
+
"""
|
|
16
|
+
Parse query params from scope
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
scope (dict): scope from consumer
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
dict: query params
|
|
23
|
+
"""
|
|
24
|
+
return parse_qs(scope["query_string"].decode("utf-8"))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TenantAwareWebsocketMiddleware:
|
|
28
|
+
def __init__(self, app):
|
|
29
|
+
self.app = app
|
|
30
|
+
|
|
31
|
+
async def __call__(self, scope, receive, send):
|
|
32
|
+
"""
|
|
33
|
+
Extract authentication token from request query params and authenticate user,
|
|
34
|
+
used by django channels.
|
|
35
|
+
|
|
36
|
+
Also sets the tenant context from the subdomain, mirroring
|
|
37
|
+
the TenantAwareHttpMiddleware logic for HTTP requests.
|
|
38
|
+
|
|
39
|
+
Parameters:
|
|
40
|
+
scope (dict): ASGI scope
|
|
41
|
+
receive (callable): ASGI receive callable
|
|
42
|
+
send (callable): ASGI send callable
|
|
43
|
+
"""
|
|
44
|
+
query_params = parse_query_params_from_scope(scope)
|
|
45
|
+
access_token = query_params.get("authorization", [None])[0]
|
|
46
|
+
if not access_token:
|
|
47
|
+
await self._reject_connection(send, "Authorization token is missing")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
tenant = self._get_tenant(scope)
|
|
51
|
+
|
|
52
|
+
async with TenantContext(tenant):
|
|
53
|
+
token_payload: TokenPayload = decode_jwt(access_token)
|
|
54
|
+
username = token_payload.get("preferred_username")
|
|
55
|
+
if not username:
|
|
56
|
+
await self._reject_connection(send, "Username cannot be extracted from the token")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
exp_timestamp = token_payload.get("exp")
|
|
60
|
+
if not exp_timestamp:
|
|
61
|
+
await self._reject_connection(send, "Token does not have an expiration time")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
user = await get_user_model().objects.filter(username=username).afirst()
|
|
65
|
+
if not user:
|
|
66
|
+
await self._reject_connection(send, "User not found.")
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
scope["user"] = user
|
|
70
|
+
scope["auth"] = token_payload
|
|
71
|
+
scope["exp_timestamp"] = exp_timestamp
|
|
72
|
+
|
|
73
|
+
return await self.app(scope, receive, send)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
async def _reject_connection(send, reason):
|
|
77
|
+
"""
|
|
78
|
+
Reject the WebSocket connection with a specific reason.
|
|
79
|
+
|
|
80
|
+
Parameters:
|
|
81
|
+
send (callable): ASGI send callable
|
|
82
|
+
reason (str): Reason for rejecting the connection
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
None
|
|
86
|
+
"""
|
|
87
|
+
await send(
|
|
88
|
+
{"type": "websocket.close", "code": 4000, "reason": reason}
|
|
89
|
+
) # Custom close code for rejection
|
|
90
|
+
|
|
91
|
+
def _get_tenant(self, scope) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Determine the tenant from the websocket scope.
|
|
94
|
+
|
|
95
|
+
In DEBUG mode, uses DEVELOPMENT_TENANT directly.
|
|
96
|
+
In production, extracts tenant from the Host header subdomain.
|
|
97
|
+
"""
|
|
98
|
+
if settings.DEBUG:
|
|
99
|
+
return DEVELOPMENT_TENANT
|
|
100
|
+
|
|
101
|
+
host = self._get_host_from_scope(scope)
|
|
102
|
+
|
|
103
|
+
if host == "testserver":
|
|
104
|
+
return "default"
|
|
105
|
+
|
|
106
|
+
parts = host.split(".")
|
|
107
|
+
|
|
108
|
+
if len(parts) >= 3:
|
|
109
|
+
tenant = parts[1].replace("-internal", "")
|
|
110
|
+
logger.debug(f"Tenant '{tenant}' extracted from websocket host: {host}")
|
|
111
|
+
return tenant
|
|
112
|
+
|
|
113
|
+
raise Exception(
|
|
114
|
+
f"Could not determine tenant from websocket subdomain. Host: {host}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _get_host_from_scope(scope) -> str:
|
|
119
|
+
"""Extract the host from ASGI scope headers."""
|
|
120
|
+
for header_name, header_value in scope.get("headers", []):
|
|
121
|
+
if header_name == b"host":
|
|
122
|
+
return header_value.decode("utf-8").split(":")[0]
|
|
123
|
+
|
|
124
|
+
# Fallback to scope["server"] if no Host header
|
|
125
|
+
server = scope.get("server")
|
|
126
|
+
if server:
|
|
127
|
+
return server[0]
|
|
128
|
+
|
|
129
|
+
raise Exception(f"Could not determine host from websocket scope: {scope}")
|
|
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.dev39 → cardo_python_utils-0.5.dev40}/python_utils/data_structures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/admin/views.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/api/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/api/drf.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/api/ninja.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/api/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/auth/service.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/celery/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/db/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/db/routers.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/db/transaction.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/db/utils.py
RENAMED
|
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.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/oidc_settings.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/redis/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/settings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/tenant_context.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/tests/__init__.py
RENAMED
|
File without changes
|
{cardo_python_utils-0.5.dev39 → cardo_python_utils-0.5.dev40}/python_utils/django/tests/conftest.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
|