cardo-python-utils 0.5.dev34__tar.gz → 0.5.dev36__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.
Files changed (61) hide show
  1. {cardo_python_utils-0.5.dev34/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev36}/PKG-INFO +1 -1
  2. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36/cardo_python_utils.egg-info}/PKG-INFO +1 -1
  3. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/pyproject.toml +1 -1
  4. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/README.md +57 -4
  5. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/admin/auth.py +12 -17
  6. cardo_python_utils-0.5.dev36/python_utils/django/admin/views.py +24 -0
  7. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/oidc_settings.py +37 -6
  8. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/tenant_context.py +35 -22
  9. cardo_python_utils-0.5.dev34/python_utils/django/admin/views.py +0 -63
  10. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/LICENSE +0 -0
  11. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/MANIFEST.in +0 -0
  12. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/README.rst +0 -0
  13. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/cardo_python_utils.egg-info/SOURCES.txt +0 -0
  14. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
  15. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/cardo_python_utils.egg-info/requires.txt +0 -0
  16. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/cardo_python_utils.egg-info/top_level.txt +0 -0
  17. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/__init__.py +0 -0
  18. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/choices.py +0 -0
  19. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/data_structures.py +0 -0
  20. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/db.py +0 -0
  21. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/__init__.py +0 -0
  22. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/admin/__init__.py +0 -0
  23. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/admin/templates/__init__.py +0 -0
  24. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/admin/templates/user_groups_changelist.html +0 -0
  25. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/admin/user_group.py +0 -0
  26. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/api/__init__.py +0 -0
  27. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/api/drf.py +0 -0
  28. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/api/ninja.py +0 -0
  29. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/api/utils.py +0 -0
  30. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/apps.py +0 -0
  31. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/auth/service.py +0 -0
  32. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/celery/__init__.py +0 -0
  33. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/celery/tenant_aware_task.py +0 -0
  34. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/db/__init__.py +0 -0
  35. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/db/routers.py +0 -0
  36. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/db/transaction.py +0 -0
  37. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/db/utils.py +0 -0
  38. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/management/__init__.py +0 -0
  39. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/management/commands/__init__.py +0 -0
  40. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/management/commands/migrateall.py +0 -0
  41. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/management/commands/tenant_aware_command.py +0 -0
  42. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/middleware/__init__.py +0 -0
  43. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/middleware/tenant_aware_http_middleware.py +0 -0
  44. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/models/__init__.py +0 -0
  45. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/models/user_group.py +0 -0
  46. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/redis/__init__.py +0 -0
  47. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/redis/key_function.py +0 -0
  48. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/settings.py +0 -0
  49. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/storage/__init__.py +0 -0
  50. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/storage/tenant_aware_storage.py +0 -0
  51. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/tests/__init__.py +0 -0
  52. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django/tests/conftest.py +0 -0
  53. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/django_utils.py +0 -0
  54. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/esma_choices.py +0 -0
  55. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/exceptions.py +0 -0
  56. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/imports.py +0 -0
  57. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/math.py +0 -0
  58. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/text.py +0 -0
  59. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/time.py +0 -0
  60. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/python_utils/types_hinting.py +0 -0
  61. {cardo_python_utils-0.5.dev34 → cardo_python_utils-0.5.dev36}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardo-python-utils
3
- Version: 0.5.dev34
3
+ Version: 0.5.dev36
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardo-python-utils
3
- Version: 0.5.dev34
3
+ Version: 0.5.dev36
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cardo-python-utils"
7
- version = "0.5.dev34"
7
+ version = "0.5.dev36"
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"
@@ -46,9 +46,7 @@ DATABASES = {
46
46
  DEVELOPMENT_TENANT = "development"
47
47
 
48
48
  # This is required to use the tenant context when routing database queries
49
- DATABASE_ROUTERS = [
50
- "python_utils.django.db.routers.TenantAwareRouter"
51
- ]
49
+ DATABASE_ROUTERS = ["python_utils.django.db.routers.TenantAwareRouter"]
52
50
 
53
51
  # If using celery, set the task class to TenantAwareTask:
54
52
  CELERY_TASK_CLS = "python_utils.django.celery.TenantAwareTask"
@@ -111,15 +109,70 @@ KEYCLOAK_USER_GROUP_MODEL = "myapp.UserGroup"
111
109
  KEYCLOAK_CONFIDENTIAL_CLIENT_ID = os.getenv("KEYCLOAK_CONFIDENTIAL_CLIENT_ID", f"{JWT_AUDIENCE}_confidential")
112
110
 
113
111
  OIDC_RP_CLIENT_ID = KEYCLOAK_CONFIDENTIAL_CLIENT_ID
114
- OIDC_RP_CLIENT_SECRET = None
115
112
  OIDC_RP_SIGN_ALGO = "RS256"
116
113
  OIDC_CREATE_USER = True
114
+ OIDC_AUTHENTICATE_CLASS = "python_utils.django.admin.views.TenantAwareOIDCAuthenticationRequestView"
117
115
 
118
116
  LOGIN_REDIRECT_URL = "/admin"
119
117
  SESSION_COOKIE_AGE = 60 * 30 # 30 minutes
120
118
  SESSION_SAVE_EVERY_REQUEST = True # Extend session on each request
121
119
  ```
122
120
 
121
+ ## urls.py file
122
+
123
+ The views of the `mozilla-django-oidc` package need to be exposed as well, for the OIDC auth:
124
+
125
+ ```python3
126
+ urlpatterns.append(path("oidc/", include("mozilla_django_oidc.urls")))
127
+ ```
128
+
129
+ ## admin.py file
130
+
131
+ The Django Admin Panel needs to be configured to automatically redirect to the OIDC login page:
132
+
133
+ ```python3
134
+ from python_utils.django.admin.auth import has_admin_site_permission
135
+ from python_utils.django.admin.views import TenantAwareOIDCAuthenticationRequestView
136
+
137
+ admin.site.login = TenantAwareOIDCAuthenticationRequestView.as_view()
138
+ admin.site.has_permission = has_admin_site_permission
139
+ ```
140
+
123
141
  ## With django-ninja
124
142
 
125
143
  If using `django-ninja`, apart from the settings configured above, auth utils are provided in the django/api/ninja.py module.
144
+
145
+ ## Testing
146
+
147
+ In order for tests to work, create the following autouse fixtures:
148
+
149
+ ```python3
150
+ import pytest
151
+
152
+ from python_utils.django.tenant_context import TenantContext
153
+
154
+ # Add this, if the test utils of the package are needed
155
+ pytest_plugins = ["python_utils.django.tests"]
156
+
157
+
158
+ @pytest.fixture(scope="session", autouse=True)
159
+ def test_database() -> str:
160
+ return "default"
161
+
162
+
163
+ @pytest.fixture(scope="session", autouse=True)
164
+ def set_tenant_context_session(test_database):
165
+ """Set tenant context for the entire test session."""
166
+ TenantContext.set(test_database)
167
+ yield
168
+ TenantContext.clear()
169
+
170
+
171
+ @pytest.fixture(autouse=True)
172
+ def set_tenant_context(set_tenant_context_session, test_database):
173
+ """Ensure tenant context is set for each test (depends on session fixture)."""
174
+ # Re-set in case it was cleared between tests
175
+ if not TenantContext.is_set():
176
+ TenantContext.set(test_database)
177
+ yield
178
+ ```
@@ -18,23 +18,18 @@ class AdminAuthenticationBackend(OIDCAuthenticationBackend):
18
18
  own Keycloak realm.
19
19
  """
20
20
 
21
- @property
22
- def OIDC_OP_TOKEN_ENDPOINT(self):
23
- """Dynamically get the token endpoint for the current tenant."""
24
-
25
- return get_oidc_op_token_endpoint()
26
-
27
- @property
28
- def OIDC_OP_USER_ENDPOINT(self):
29
- """Dynamically get the userinfo endpoint for the current tenant."""
30
-
31
- return get_oidc_op_user_endpoint()
32
-
33
- @property
34
- def OIDC_OP_JWKS_ENDPOINT(self):
35
- """Dynamically get the JWKS endpoint for the current tenant."""
36
-
37
- return get_oidc_op_jwks_endpoint()
21
+ def get_settings(self, attr, *args):
22
+ if attr == "OIDC_OP_TOKEN_ENDPOINT":
23
+ return get_oidc_op_token_endpoint()
24
+ if attr == "OIDC_OP_USER_ENDPOINT":
25
+ return get_oidc_op_user_endpoint()
26
+ if attr == "OIDC_OP_JWKS_ENDPOINT":
27
+ return get_oidc_op_jwks_endpoint()
28
+ if attr == "OIDC_RP_CLIENT_SECRET":
29
+ # The explicit return of None is needed to prevent the parent class from raising an error
30
+ return None
31
+
32
+ return super().get_settings(attr, *args)
38
33
 
39
34
  def get_token(self, payload):
40
35
  # Instead of passing client_id and client_secret,
@@ -0,0 +1,24 @@
1
+ """
2
+ Tenant-aware OIDC views for mozilla-django-oidc.
3
+
4
+ These views override the default mozilla-django-oidc views to dynamically
5
+ resolve OIDC endpoints based on the current tenant context.
6
+ """
7
+
8
+ from mozilla_django_oidc.views import OIDCAuthenticationRequestView
9
+
10
+ from ..oidc_settings import get_oidc_op_authorization_endpoint
11
+
12
+
13
+ class TenantAwareOIDCAuthenticationRequestView(OIDCAuthenticationRequestView):
14
+ """
15
+ Tenant-aware OIDC authentication request view.
16
+
17
+ Dynamically resolves the authorization endpoint based on the current tenant.
18
+ """
19
+
20
+ def get_settings(self, attr, *args):
21
+ if attr == "OIDC_OP_AUTHORIZATION_ENDPOINT":
22
+ return get_oidc_op_authorization_endpoint()
23
+
24
+ return super().get_settings(attr, *args)
@@ -8,17 +8,30 @@ import json
8
8
  import os
9
9
  import requests
10
10
 
11
+ from django.conf import settings
11
12
  from .tenant_context import TenantContext
12
13
 
13
14
 
14
15
  KEYCLOAK_SERVER_URL = os.getenv("KEYCLOAK_SERVER_URL", None)
15
16
  KEYCLOAK_CONFIDENTIAL_CLIENT_ID = os.getenv("KEYCLOAK_CONFIDENTIAL_CLIENT_ID", None)
17
+
18
+ OIDC_CLIENT_AUTH_METHOD = getattr(settings, "OIDC_CLIENT_AUTH_METHOD", "client_assertion")
19
+ if OIDC_CLIENT_AUTH_METHOD not in ("client_assertion", "client_secret"):
20
+ raise ValueError(
21
+ f"Invalid OIDC_CLIENT_AUTH_METHOD: {OIDC_CLIENT_AUTH_METHOD}. "
22
+ f"Supported methods are 'client_assertion' and 'client_secret'."
23
+ )
24
+
16
25
  KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATHS: dict[str, str] = json.loads(
17
26
  os.getenv("KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATHS", "{}")
18
27
  )
19
28
  KEYCLOAK_CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"
20
29
  KEYCLOAK_CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
21
30
 
31
+ KEYCLOAK_CONFIDENTIAL_CLIENT_SECRETS: dict[str, str] = json.loads(
32
+ os.getenv("KEYCLOAK_CONFIDENTIAL_CLIENT_SECRETS", "{}")
33
+ )
34
+
22
35
 
23
36
  def get_oidc_op_base_url() -> str:
24
37
  """Get the base URL for the OIDC provider (Keycloak realm URL)."""
@@ -65,20 +78,38 @@ def get_confidential_client_service_account_token() -> str:
65
78
  return token
66
79
 
67
80
 
81
+ def get_confidential_client_secret() -> str:
82
+ """
83
+ Retrieves the Keycloak confidential client secret for the current tenant.
84
+ """
85
+ tenant = TenantContext.get()
86
+ client_secret = KEYCLOAK_CONFIDENTIAL_CLIENT_SECRETS.get(tenant)
87
+ if not client_secret:
88
+ raise ValueError(f"Keycloak confidential client secret for tenant {tenant} not found.")
89
+
90
+ return client_secret
91
+
92
+
68
93
  def get_oidc_confidential_client_token(**kwargs) -> dict:
69
94
  """
70
95
  Obtains token for an OIDC confidential client with the client credentials grant,
71
96
  using a service account token for authentication.
72
97
  """
73
98
 
99
+ data = {
100
+ "grant_type": KEYCLOAK_CLIENT_CREDENTIALS_GRANT_TYPE,
101
+ **kwargs,
102
+ }
103
+ if OIDC_CLIENT_AUTH_METHOD == "client_secret":
104
+ data["client_id"] = KEYCLOAK_CONFIDENTIAL_CLIENT_ID
105
+ data["client_secret"] = get_confidential_client_secret()
106
+ else:
107
+ data["client_assertion_type"] = KEYCLOAK_CLIENT_ASSERTION_TYPE
108
+ data["client_assertion"] = get_confidential_client_service_account_token()
109
+
74
110
  response = requests.post(
75
111
  get_oidc_op_token_endpoint(),
76
- data={
77
- "grant_type": KEYCLOAK_CLIENT_CREDENTIALS_GRANT_TYPE,
78
- "client_assertion_type": KEYCLOAK_CLIENT_ASSERTION_TYPE,
79
- "client_assertion": get_confidential_client_service_account_token(),
80
- **kwargs,
81
- },
112
+ data=data,
82
113
  )
83
114
  response.raise_for_status()
84
115
 
@@ -1,22 +1,27 @@
1
1
  import logging
2
2
  import sys
3
3
  from contextlib import ContextDecorator
4
- from threading import local
4
+ from contextvars import ContextVar
5
5
  from typing import Optional
6
6
 
7
7
  from .settings import DATABASES
8
8
 
9
9
  logger = logging.getLogger(__name__)
10
- thread_namespace = local()
10
+
11
+ # ContextVar propagates automatically to async threads via sync_to_async
12
+ _tenant_var: ContextVar[Optional[str]] = ContextVar('tenant', default=None)
11
13
 
12
14
 
13
15
  class TenantContext(ContextDecorator):
14
16
  """
15
17
  Context manager that sets the current tenant.
16
18
  This class can be used in several ways:
17
- 1. As a context manager:
19
+ 1. As a context manager (sync and async):
18
20
  with TenantContext('tenant'):
19
21
  # do something
22
+
23
+ async with TenantContext('tenant'):
24
+ # do something async
20
25
 
21
26
  2. As a decorator:
22
27
  @TenantContext('tenant')
@@ -33,23 +38,32 @@ class TenantContext(ContextDecorator):
33
38
 
34
39
  def __init__(self, tenant: str):
35
40
  self.tenant = tenant
41
+ self._token = None
36
42
 
37
43
  def __enter__(self):
38
- TenantContext.set(self.tenant)
44
+ self._token = TenantContext.set(self.tenant)
45
+ return self
39
46
 
40
47
  def __exit__(self, exc_type, exc_val, exc_tb):
41
- TenantContext.clear()
48
+ TenantContext.clear(self._token)
49
+
50
+ async def __aenter__(self):
51
+ self._token = TenantContext.set(self.tenant)
52
+ return self
53
+
54
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
55
+ TenantContext.clear(self._token)
42
56
 
43
57
  @staticmethod
44
58
  def is_set() -> bool:
45
- return hasattr(thread_namespace, TenantContext.field_name)
59
+ return _tenant_var.get() is not None
46
60
 
47
61
  @staticmethod
48
62
  def get() -> Optional[str]:
49
- try:
50
- return getattr(thread_namespace, TenantContext.field_name)
51
- except AttributeError as e:
52
- raise RuntimeError("Tenant context is not set.") from e
63
+ tenant = _tenant_var.get()
64
+ if tenant is None:
65
+ raise RuntimeError("Tenant context is not set.")
66
+ return tenant
53
67
 
54
68
  @staticmethod
55
69
  def set(tenant):
@@ -66,24 +80,23 @@ class TenantContext(ContextDecorator):
66
80
  logger.error("ERROR: TENANT CONTEXT ALREADY SET")
67
81
  logger.error(f"Current tenant: {TenantContext.get()}, new tenant: {tenant}")
68
82
  return sys.exit(1)
69
-
70
83
  else:
71
- # Normally in a request-response cycle, or a single task, the
72
- # tenant context is set only once. But there is an exception
73
- # for the migrate command, which sets the tenant context
74
- # multiple times to the same value.
75
- logger.info("Tenant context already set to %s", tenant)
76
- return
77
-
84
+ # If the tenant is already set to the same value, we do nothing and return None.
85
+ return None
86
+
78
87
  if tenant not in DATABASES:
79
88
  logger.error(f"Tenant '{tenant}' not found in DATABASES settings.")
80
89
  return sys.exit(1)
81
90
 
82
- setattr(thread_namespace, TenantContext.field_name, tenant)
91
+ token = _tenant_var.set(tenant)
83
92
  logger.info(f"Tenant context set to {tenant}")
93
+ return token
84
94
 
85
95
  @staticmethod
86
- def clear():
96
+ def clear(token=None):
87
97
  if TenantContext.is_set():
88
- delattr(thread_namespace, TenantContext.field_name)
89
- logger.info("Tenant context cleared")
98
+ if token is not None:
99
+ _tenant_var.reset(token)
100
+ else:
101
+ _tenant_var.set(None)
102
+ logger.info("Tenant context cleared")
@@ -1,63 +0,0 @@
1
- """
2
- Tenant-aware OIDC views for mozilla-django-oidc.
3
-
4
- These views override the default mozilla-django-oidc views to dynamically
5
- resolve OIDC endpoints based on the current tenant context.
6
- """
7
-
8
- from mozilla_django_oidc.views import (
9
- OIDCAuthenticationCallbackView,
10
- OIDCAuthenticationRequestView,
11
- OIDCLogoutView,
12
- )
13
-
14
- from ..oidc_settings import (
15
- get_oidc_op_authorization_endpoint,
16
- get_oidc_op_logout_endpoint,
17
- )
18
-
19
-
20
- class TenantAwareOIDCAuthenticationRequestView(OIDCAuthenticationRequestView):
21
- """
22
- Tenant-aware OIDC authentication request view.
23
-
24
- Dynamically resolves the authorization endpoint based on the current tenant.
25
- """
26
-
27
- @property
28
- def OIDC_OP_AUTH_ENDPOINT(self):
29
- """Dynamically get the authorization endpoint for the current tenant."""
30
- return get_oidc_op_authorization_endpoint()
31
-
32
- def get_settings(self, attr, *args):
33
- if attr == "OIDC_OP_AUTHORIZATION_ENDPOINT":
34
- return self.OIDC_OP_AUTH_ENDPOINT
35
- return super().get_settings(attr, *args)
36
-
37
-
38
- class TenantAwareOIDCAuthenticationCallbackView(OIDCAuthenticationCallbackView):
39
- """
40
- Tenant-aware OIDC authentication callback view.
41
-
42
- Uses the tenant-aware authentication backend.
43
- """
44
-
45
- pass
46
-
47
-
48
- class TenantAwareOIDCLogoutView(OIDCLogoutView):
49
- """
50
- Tenant-aware OIDC logout view.
51
-
52
- Dynamically resolves the logout endpoint based on the current tenant.
53
- """
54
-
55
- @property
56
- def OIDC_OP_LOGOUT_ENDPOINT(self):
57
- """Dynamically get the logout endpoint for the current tenant."""
58
- return get_oidc_op_logout_endpoint()
59
-
60
- def get_settings(self, attr, *args):
61
- if attr == "OIDC_OP_LOGOUT_ENDPOINT":
62
- return self.OIDC_OP_LOGOUT_ENDPOINT
63
- return super().get_settings(attr, *args)