karrio-server-core 2025.5rc1__py3-none-any.whl

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.

Potentially problematic release.


This version of karrio-server-core might be problematic. Click here for more details.

Files changed (241) hide show
  1. karrio/server/conf.py +54 -0
  2. karrio/server/core/__init__.py +3 -0
  3. karrio/server/core/admin.py +1 -0
  4. karrio/server/core/apps.py +10 -0
  5. karrio/server/core/authentication.py +313 -0
  6. karrio/server/core/context_processors.py +12 -0
  7. karrio/server/core/datatypes.py +369 -0
  8. karrio/server/core/dataunits.py +156 -0
  9. karrio/server/core/exceptions.py +200 -0
  10. karrio/server/core/fields.py +12 -0
  11. karrio/server/core/filters.py +823 -0
  12. karrio/server/core/gateway.py +720 -0
  13. karrio/server/core/management/commands/cli.py +19 -0
  14. karrio/server/core/management/commands/create_oauth_client.py +41 -0
  15. karrio/server/core/middleware.py +95 -0
  16. karrio/server/core/migrations/0001_initial.py +28 -0
  17. karrio/server/core/migrations/0002_apilogindex.py +69 -0
  18. karrio/server/core/migrations/0003_apilogindex_test_mode.py +62 -0
  19. karrio/server/core/migrations/0004_metafield.py +74 -0
  20. karrio/server/core/migrations/0005_alter_metafield_type_alter_metafield_value.py +23 -0
  21. karrio/server/core/migrations/__init__.py +0 -0
  22. karrio/server/core/models/__init__.py +48 -0
  23. karrio/server/core/models/base.py +70 -0
  24. karrio/server/core/models/entity.py +22 -0
  25. karrio/server/core/models/metafield.py +144 -0
  26. karrio/server/core/models/third_party.py +21 -0
  27. karrio/server/core/oauth_validators.py +171 -0
  28. karrio/server/core/permissions.py +37 -0
  29. karrio/server/core/renderers.py +11 -0
  30. karrio/server/core/router.py +3 -0
  31. karrio/server/core/serializers.py +1898 -0
  32. karrio/server/core/signals.py +57 -0
  33. karrio/server/core/tests.py +98 -0
  34. karrio/server/core/urls.py +12 -0
  35. karrio/server/core/utils.py +479 -0
  36. karrio/server/core/validators.py +416 -0
  37. karrio/server/core/views/__init__.py +2 -0
  38. karrio/server/core/views/api.py +133 -0
  39. karrio/server/core/views/metadata.py +44 -0
  40. karrio/server/core/views/oauth.py +74 -0
  41. karrio/server/core/views/references.py +82 -0
  42. karrio/server/core/views/schema.py +310 -0
  43. karrio/server/filters/__init__.py +2 -0
  44. karrio/server/filters/abstract.py +26 -0
  45. karrio/server/iam/__init__.py +0 -0
  46. karrio/server/iam/admin.py +3 -0
  47. karrio/server/iam/apps.py +21 -0
  48. karrio/server/iam/migrations/0001_initial.py +33 -0
  49. karrio/server/iam/migrations/__init__.py +0 -0
  50. karrio/server/iam/models.py +48 -0
  51. karrio/server/iam/permissions.py +134 -0
  52. karrio/server/iam/serializers.py +39 -0
  53. karrio/server/iam/signals.py +20 -0
  54. karrio/server/iam/tests.py +3 -0
  55. karrio/server/iam/views.py +3 -0
  56. karrio/server/openapi.py +75 -0
  57. karrio/server/providers/__init__.py +1 -0
  58. karrio/server/providers/admin.py +364 -0
  59. karrio/server/providers/apps.py +10 -0
  60. karrio/server/providers/extension/__init__.py +1 -0
  61. karrio/server/providers/extension/models/__init__.py +1 -0
  62. karrio/server/providers/extension/models/allied_express.py +22 -0
  63. karrio/server/providers/extension/models/allied_express_local.py +22 -0
  64. karrio/server/providers/extension/models/amazon_shipping.py +27 -0
  65. karrio/server/providers/extension/models/aramex.py +25 -0
  66. karrio/server/providers/extension/models/asendia_us.py +21 -0
  67. karrio/server/providers/extension/models/australiapost.py +20 -0
  68. karrio/server/providers/extension/models/boxknight.py +19 -0
  69. karrio/server/providers/extension/models/bpost.py +21 -0
  70. karrio/server/providers/extension/models/canadapost.py +21 -0
  71. karrio/server/providers/extension/models/canpar.py +19 -0
  72. karrio/server/providers/extension/models/chronopost.py +22 -0
  73. karrio/server/providers/extension/models/colissimo.py +22 -0
  74. karrio/server/providers/extension/models/dhl_express.py +23 -0
  75. karrio/server/providers/extension/models/dhl_parcel_de.py +25 -0
  76. karrio/server/providers/extension/models/dhl_poland.py +22 -0
  77. karrio/server/providers/extension/models/dhl_universal.py +19 -0
  78. karrio/server/providers/extension/models/dicom.py +20 -0
  79. karrio/server/providers/extension/models/dpd.py +37 -0
  80. karrio/server/providers/extension/models/dpdhl.py +26 -0
  81. karrio/server/providers/extension/models/easypost.py +20 -0
  82. karrio/server/providers/extension/models/eshipper.py +21 -0
  83. karrio/server/providers/extension/models/fedex.py +25 -0
  84. karrio/server/providers/extension/models/fedex_ws.py +24 -0
  85. karrio/server/providers/extension/models/freightcom.py +21 -0
  86. karrio/server/providers/extension/models/generic.py +35 -0
  87. karrio/server/providers/extension/models/geodis.py +22 -0
  88. karrio/server/providers/extension/models/hay_post.py +22 -0
  89. karrio/server/providers/extension/models/laposte.py +19 -0
  90. karrio/server/providers/extension/models/locate2u.py +22 -0
  91. karrio/server/providers/extension/models/nationex.py +22 -0
  92. karrio/server/providers/extension/models/purolator.py +21 -0
  93. karrio/server/providers/extension/models/roadie.py +18 -0
  94. karrio/server/providers/extension/models/royalmail.py +19 -0
  95. karrio/server/providers/extension/models/sendle.py +22 -0
  96. karrio/server/providers/extension/models/tge.py +63 -0
  97. karrio/server/providers/extension/models/tnt.py +23 -0
  98. karrio/server/providers/extension/models/ups.py +23 -0
  99. karrio/server/providers/extension/models/usps.py +23 -0
  100. karrio/server/providers/extension/models/usps_international.py +23 -0
  101. karrio/server/providers/extension/models/usps_wt.py +24 -0
  102. karrio/server/providers/extension/models/usps_wt_international.py +24 -0
  103. karrio/server/providers/extension/models/zoom2u.py +23 -0
  104. karrio/server/providers/migrations/0001_initial.py +140 -0
  105. karrio/server/providers/migrations/0002_carrier_active.py +18 -0
  106. karrio/server/providers/migrations/0003_auto_20201230_0820.py +24 -0
  107. karrio/server/providers/migrations/0004_auto_20210212_0554.py +178 -0
  108. karrio/server/providers/migrations/0005_auto_20210212_0555.py +18 -0
  109. karrio/server/providers/migrations/0006_australiapostsettings.py +29 -0
  110. karrio/server/providers/migrations/0007_auto_20210213_0206.py +21 -0
  111. karrio/server/providers/migrations/0008_auto_20210214_0409.py +30 -0
  112. karrio/server/providers/migrations/0009_auto_20210308_0302.py +18 -0
  113. karrio/server/providers/migrations/0010_auto_20210409_0852.py +32 -0
  114. karrio/server/providers/migrations/0011_auto_20210409_0853.py +21 -0
  115. karrio/server/providers/migrations/0012_alter_carrier_options.py +17 -0
  116. karrio/server/providers/migrations/0013_tntsettings.py +30 -0
  117. karrio/server/providers/migrations/0014_auto_20210612_1608.py +46 -0
  118. karrio/server/providers/migrations/0015_auto_20210615_1601.py +28 -0
  119. karrio/server/providers/migrations/0016_alter_purolatorsettings_user_token.py +18 -0
  120. karrio/server/providers/migrations/0017_auto_20210805_0359.py +1293 -0
  121. karrio/server/providers/migrations/0018_alter_fedexsettings_user_key.py +18 -0
  122. karrio/server/providers/migrations/0019_dhlpolandsettings_servicelevel.py +65 -0
  123. karrio/server/providers/migrations/0020_genericsettings_labeltemplate.py +52 -0
  124. karrio/server/providers/migrations/0021_auto_20211231_2353.py +40 -0
  125. karrio/server/providers/migrations/0022_carrier_metadata.py +18 -0
  126. karrio/server/providers/migrations/0023_auto_20220124_1916.py +27 -0
  127. karrio/server/providers/migrations/0024_alter_genericsettings_custom_carrier_name.py +19 -0
  128. karrio/server/providers/migrations/0025_alter_servicelevel_service_code.py +19 -0
  129. karrio/server/providers/migrations/0026_auto_20220208_0132.py +59 -0
  130. karrio/server/providers/migrations/0027_auto_20220304_1340.py +29 -0
  131. karrio/server/providers/migrations/0028_auto_20220323_1500.py +33 -0
  132. karrio/server/providers/migrations/0029_easypostsettings.py +27 -0
  133. karrio/server/providers/migrations/0030_amazonmwssettings.py +29 -0
  134. karrio/server/providers/migrations/0031_delete_amazonmwssettings.py +18 -0
  135. karrio/server/providers/migrations/0032_alter_carrier_test.py +18 -0
  136. karrio/server/providers/migrations/0033_auto_20220708_1350.py +22 -0
  137. karrio/server/providers/migrations/0034_amazonmwssettings_dpdhlsettings.py +47 -0
  138. karrio/server/providers/migrations/0035_alter_carrier_capabilities.py +43 -0
  139. karrio/server/providers/migrations/0036_upsfreightsettings.py +31 -0
  140. karrio/server/providers/migrations/0037_chronopostsettings.py +29 -0
  141. karrio/server/providers/migrations/0038_alter_genericsettings_label_template.py +19 -0
  142. karrio/server/providers/migrations/0039_auto_20220906_0612.py +23 -0
  143. karrio/server/providers/migrations/0040_dpdhlsettings_services.py +18 -0
  144. karrio/server/providers/migrations/0041_auto_20221105_0705.py +38 -0
  145. karrio/server/providers/migrations/0042_auto_20221215_1642.py +23 -0
  146. karrio/server/providers/migrations/0043_alter_genericsettings_account_number_and_more.py +39 -0
  147. karrio/server/providers/migrations/0044_carrier_carrier_capabilities.py +64 -0
  148. karrio/server/providers/migrations/0045_alter_carrier_active_alter_carrier_carrier_id.py +31 -0
  149. karrio/server/providers/migrations/0046_remove_dpdhlsettings_signature_and_more.py +41 -0
  150. karrio/server/providers/migrations/0047_dpdsettings.py +286 -0
  151. karrio/server/providers/migrations/0048_servicelevel_min_weight_servicelevel_transit_days_and_more.py +64 -0
  152. karrio/server/providers/migrations/0049_boxknightsettings_geodissettings_lapostesettings_and_more.py +156 -0
  153. karrio/server/providers/migrations/0050_carrier_is_system_alter_carrier_metadata_and_more.py +106 -0
  154. karrio/server/providers/migrations/0051_rename_username_upssettings_client_id_and_more.py +31 -0
  155. karrio/server/providers/migrations/0052_alter_upssettings_account_number_and_more.py +20 -0
  156. karrio/server/providers/migrations/0053_locate2usettings.py +281 -0
  157. karrio/server/providers/migrations/0054_zoom2usettings.py +280 -0
  158. karrio/server/providers/migrations/0055_rename_amazonmwssettings_amazonshippingsettings_and_more.py +44 -0
  159. karrio/server/providers/migrations/0056_asendiaussettings_geodissettings_code_client_and_more.py +75 -0
  160. karrio/server/providers/migrations/0057_alter_servicelevel_weight_unit_belgianpostsettings.py +51 -0
  161. karrio/server/providers/migrations/0058_alliedexpresssettings.py +38 -0
  162. karrio/server/providers/migrations/0059_ratesheet.py +81 -0
  163. karrio/server/providers/migrations/0060_belgianpostsettings_rate_sheet_and_more.py +73 -0
  164. karrio/server/providers/migrations/0061_alliedexpresssettings_service_type.py +17 -0
  165. karrio/server/providers/migrations/0062_sendlesettings_account_country_code.py +257 -0
  166. karrio/server/providers/migrations/0063_servicelevel_metadata.py +25 -0
  167. karrio/server/providers/migrations/0064_alliedexpresslocalsettings.py +43 -0
  168. karrio/server/providers/migrations/0065_servicelevel_carrier_service_code_and_more.py +66 -0
  169. karrio/server/providers/migrations/0066_rename_fedexsettings_fedexwssettings_and_more.py +28 -0
  170. karrio/server/providers/migrations/0067_fedexsettings.py +283 -0
  171. karrio/server/providers/migrations/0068_fedexsettings_track_api_key_and_more.py +38 -0
  172. karrio/server/providers/migrations/0069_alter_canadapostsettings_contract_id_and_more.py +23 -0
  173. karrio/server/providers/migrations/0070_tgesettings_alter_carrier_capabilities.py +65 -0
  174. karrio/server/providers/migrations/0071_alter_tgesettings_my_toll_token.py +18 -0
  175. karrio/server/providers/migrations/0072_rename_eshippersettings_eshipperxmlsettings_and_more.py +28 -0
  176. karrio/server/providers/migrations/0073_delete_eshipperxmlsettings.py +41 -0
  177. karrio/server/providers/migrations/0074_eshippersettings.py +38 -0
  178. karrio/server/providers/migrations/0075_haypostsettings.py +40 -0
  179. karrio/server/providers/migrations/0076_rename_customer_registration_id_uspsinternationalsettings_account_number_and_more.py +125 -0
  180. karrio/server/providers/migrations/0077_uspswtinternationalsettings_uspswtsettings_and_more.py +165 -0
  181. karrio/server/providers/migrations/0078_auto_20240813_1552.py +120 -0
  182. karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py +31 -0
  183. karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py +3025 -0
  184. karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py +338 -0
  185. karrio/server/providers/migrations/__init__.py +0 -0
  186. karrio/server/providers/models/__init__.py +17 -0
  187. karrio/server/providers/models/carrier.py +309 -0
  188. karrio/server/providers/models/config.py +30 -0
  189. karrio/server/providers/models/service.py +62 -0
  190. karrio/server/providers/models/sheet.py +60 -0
  191. karrio/server/providers/models/template.py +39 -0
  192. karrio/server/providers/models/utils.py +58 -0
  193. karrio/server/providers/router.py +3 -0
  194. karrio/server/providers/serializers/__init__.py +3 -0
  195. karrio/server/providers/serializers/base.py +277 -0
  196. karrio/server/providers/signals.py +27 -0
  197. karrio/server/providers/tests.py +3 -0
  198. karrio/server/providers/urls.py +11 -0
  199. karrio/server/providers/views/__init__.py +0 -0
  200. karrio/server/providers/views/carriers.py +269 -0
  201. karrio/server/providers/views/connections.py +176 -0
  202. karrio/server/samples.py +352 -0
  203. karrio/server/serializers/__init__.py +2 -0
  204. karrio/server/serializers/abstract.py +506 -0
  205. karrio/server/tracing/__init__.py +0 -0
  206. karrio/server/tracing/admin.py +63 -0
  207. karrio/server/tracing/apps.py +8 -0
  208. karrio/server/tracing/migrations/0001_initial.py +41 -0
  209. karrio/server/tracing/migrations/0002_auto_20220710_1307.py +22 -0
  210. karrio/server/tracing/migrations/0003_auto_20221105_0317.py +43 -0
  211. karrio/server/tracing/migrations/0004_tracingrecord_carrier_account_idx.py +24 -0
  212. karrio/server/tracing/migrations/0005_optimise_tracingrecord_request_log_idx.py +25 -0
  213. karrio/server/tracing/migrations/0006_alter_tracingrecord_options_and_more.py +49 -0
  214. karrio/server/tracing/migrations/__init__.py +0 -0
  215. karrio/server/tracing/models.py +80 -0
  216. karrio/server/tracing/tests.py +3 -0
  217. karrio/server/tracing/utils.py +112 -0
  218. karrio/server/user/__init__.py +0 -0
  219. karrio/server/user/admin.py +96 -0
  220. karrio/server/user/apps.py +7 -0
  221. karrio/server/user/forms.py +35 -0
  222. karrio/server/user/migrations/0001_initial.py +41 -0
  223. karrio/server/user/migrations/0002_token.py +29 -0
  224. karrio/server/user/migrations/0003_token_test_mode.py +20 -0
  225. karrio/server/user/migrations/0004_group.py +26 -0
  226. karrio/server/user/migrations/0005_token_label.py +21 -0
  227. karrio/server/user/migrations/0006_workspaceconfig.py +63 -0
  228. karrio/server/user/migrations/__init__.py +0 -0
  229. karrio/server/user/models.py +203 -0
  230. karrio/server/user/serializers.py +46 -0
  231. karrio/server/user/templates/registration/login.html +108 -0
  232. karrio/server/user/templates/registration/registration_confirm_email.html +10 -0
  233. karrio/server/user/templates/registration/registration_confirm_email.txt +3 -0
  234. karrio/server/user/tests.py +3 -0
  235. karrio/server/user/urls.py +10 -0
  236. karrio/server/user/utils.py +60 -0
  237. karrio/server/user/views.py +9 -0
  238. karrio_server_core-2025.5rc1.dist-info/METADATA +32 -0
  239. karrio_server_core-2025.5rc1.dist-info/RECORD +241 -0
  240. karrio_server_core-2025.5rc1.dist-info/WHEEL +5 -0
  241. karrio_server_core-2025.5rc1.dist-info/top_level.txt +2 -0
karrio/server/conf.py ADDED
@@ -0,0 +1,54 @@
1
+ from django.db import connection
2
+ from django.conf import settings as base_settings
3
+
4
+ FEATURE_FLAGS = {
5
+ k: getattr(base_settings, k, True) for k, _ in base_settings.FEATURE_FLAGS
6
+ }
7
+ DEFAULT_ALLOWED_CONFIG = [
8
+ "APP_NAME",
9
+ "APP_WEBSITE",
10
+ "SUPPORT_EMAIL",
11
+ "BASE_TEMPLATE",
12
+ "BASE_FOOTER_TEMPLATE",
13
+ ]
14
+ FALLBACK_VALUES = {
15
+ "APP_NAME": "Karrio",
16
+ "APP_WEBSITE": "https://karrio.io",
17
+ "SUPPORT_EMAIL": "hello@karrio.io",
18
+ "BASE_TEMPLATE": "karrio/base_site.html",
19
+ "BASE_FOOTER_TEMPLATE": "karrio/base_footer.html",
20
+ }
21
+
22
+
23
+ class _Settings:
24
+ def __getattr__(self, item):
25
+ if item == "tenant":
26
+ return self._get_tenant()
27
+
28
+ if item == "schema":
29
+ return self._get_schema()
30
+
31
+ if item == "APP_NAME":
32
+ return getattr(self._get_tenant(), "name", FALLBACK_VALUES.get(item))
33
+
34
+ if item == "APP_WEBSITE":
35
+ return getattr(self._get_tenant(), "website", FALLBACK_VALUES.get(item))
36
+
37
+ if item in FEATURE_FLAGS and self._get_tenant() is not None:
38
+ feature_flags = getattr(self._get_tenant(), "feature_flags", {})
39
+
40
+ return feature_flags.get(item, getattr(base_settings, item, None))
41
+
42
+ return getattr(base_settings, item, FALLBACK_VALUES.get(item))
43
+
44
+ def _get_schema(self):
45
+ return connection.get_schema() if base_settings.MULTI_TENANTS else None
46
+
47
+ def _get_tenant(self):
48
+ return connection.get_tenant() if base_settings.MULTI_TENANTS else None
49
+
50
+ def get(self, item):
51
+ return getattr(self, item)
52
+
53
+
54
+ settings = _Settings()
@@ -0,0 +1,3 @@
1
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore
2
+
3
+ default_app_config = 'karrio.server.core.apps.CoreConfig'
@@ -0,0 +1 @@
1
+ from django.contrib import admin
@@ -0,0 +1,10 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CoreConfig(AppConfig):
5
+ name = "karrio.server.core"
6
+
7
+ def ready(self):
8
+ from karrio.server.core.signals import register_signals
9
+
10
+ register_signals()
@@ -0,0 +1,313 @@
1
+ import yaml # type: ignore
2
+ import pydoc
3
+ import logging
4
+ import functools
5
+ from django.db.utils import ProgrammingError
6
+ from django.conf import settings
7
+ from django.contrib.auth import mixins, get_user_model
8
+ from django.utils.translation import gettext_lazy as _
9
+ from django.utils.functional import SimpleLazyObject
10
+ from django.contrib.auth.middleware import (
11
+ AuthenticationMiddleware as BaseAuthenticationMiddleware,
12
+ )
13
+ from rest_framework import status
14
+ from rest_framework import exceptions
15
+ from rest_framework.authentication import (
16
+ TokenAuthentication as BaseTokenAuthentication,
17
+ BasicAuthentication as BaseBasicAuthentication,
18
+ )
19
+ from rest_framework_simplejwt.authentication import (
20
+ JWTAuthentication as BaseJWTAuthentication,
21
+ )
22
+ from oauth2_provider.contrib.rest_framework import (
23
+ OAuth2Authentication as BaseOAuth2Authentication,
24
+ )
25
+ from django_otp.middleware import OTPMiddleware
26
+
27
+ logger = logging.getLogger(__name__)
28
+ UserModel = get_user_model()
29
+ AUTHENTICATION_CLASSES = getattr(settings, "AUTHENTICATION_CLASSES", [])
30
+
31
+
32
+ def catch_auth_exception(func):
33
+ @functools.wraps(func)
34
+ def wrapper(*args, **kwargs):
35
+ try:
36
+ return func(*args, **kwargs)
37
+ except exceptions.AuthenticationFailed:
38
+ from karrio.server.core.exceptions import APIException
39
+
40
+ raise APIException(
41
+ "Given token not valid for any token type",
42
+ code="invalid_token",
43
+ status_code=status.HTTP_401_UNAUTHORIZED,
44
+ )
45
+
46
+ return wrapper
47
+
48
+
49
+ class TokenAuthentication(BaseTokenAuthentication):
50
+ def get_model(self):
51
+ if self.model is not None:
52
+ return self.model
53
+ from karrio.server.user.models import Token
54
+
55
+ return Token
56
+
57
+ @catch_auth_exception
58
+ def authenticate(self, request):
59
+ auth = super().authenticate(request)
60
+
61
+ if auth is not None:
62
+ user, token = auth
63
+ request.user = user or request.user
64
+ request.token = token
65
+ request.test_mode = token.test_mode
66
+ request.org = SimpleLazyObject(
67
+ functools.partial(
68
+ get_request_org,
69
+ request,
70
+ user,
71
+ default_org=token.organization,
72
+ )
73
+ )
74
+
75
+ return auth
76
+
77
+
78
+ class TokenBasicAuthentication(BaseBasicAuthentication):
79
+ @catch_auth_exception
80
+ def authenticate(self, request):
81
+ auth = super(TokenBasicAuthentication, self).authenticate(request)
82
+
83
+ if auth is not None:
84
+ user, token = auth
85
+ request.user = user or request.user
86
+ request.token = token
87
+ request.test_mode = token.test_mode
88
+ request.org = SimpleLazyObject(
89
+ functools.partial(
90
+ get_request_org,
91
+ request,
92
+ user,
93
+ default_org=token.organization,
94
+ )
95
+ )
96
+
97
+ return auth
98
+
99
+ def authenticate_credentials(self, api_key, *args, **kwargs):
100
+ """
101
+ Authenticate the api token with optional request for context.
102
+ """
103
+ from karrio.server.user.models import Token
104
+
105
+ token = Token.objects.filter(key=api_key).first()
106
+ user = getattr(token, "user", None)
107
+
108
+ if user is None:
109
+ raise exceptions.AuthenticationFailed(_("Invalid username/password."))
110
+
111
+ if not user.is_active:
112
+ raise exceptions.AuthenticationFailed(_("User inactive or deleted."))
113
+
114
+ return (user, token)
115
+
116
+
117
+ class JWTAuthentication(BaseJWTAuthentication):
118
+ @catch_auth_exception
119
+ def authenticate(self, request):
120
+ auth = super().authenticate(request)
121
+
122
+ if auth is not None:
123
+ user, token = auth
124
+
125
+ request.user = user
126
+ request.token = token
127
+ request.test_mode = get_request_test_mode(request)
128
+ request.otp_is_verified = token.get("is_verified") or False
129
+ request.org = SimpleLazyObject(
130
+ functools.partial(
131
+ get_request_org,
132
+ request,
133
+ user,
134
+ org_id=request.META.get("HTTP_X_ORG_ID"),
135
+ )
136
+ )
137
+
138
+ if not token.get("is_verified"):
139
+ raise exceptions.AuthenticationFailed(
140
+ _("Authentication token not verified"), code="otp_not_verified"
141
+ )
142
+
143
+ return auth
144
+
145
+
146
+ class OAuth2Authentication(BaseOAuth2Authentication):
147
+ @catch_auth_exception
148
+ def authenticate(self, request):
149
+ auth = super().authenticate(request)
150
+
151
+ if auth is not None:
152
+ user, token = auth
153
+
154
+ # Enhanced user context handling for different OAuth flows
155
+ if user is None:
156
+ # For client credentials flow, the user might be None from the base class
157
+ # but our custom validator should have set it on the request
158
+ if hasattr(request, 'user') and request.user and not request.user.is_anonymous:
159
+ user = request.user
160
+ elif hasattr(request, 'oauth_user'):
161
+ user = request.oauth_user
162
+ # If we still don't have a user, try to get it from the OAuth application
163
+ elif hasattr(token, 'application') and token.application:
164
+ user = getattr(token.application, 'user', None)
165
+
166
+ # Set request context
167
+ request.user = user or request.user
168
+ request.token = token
169
+ request.test_mode = get_request_test_mode(request)
170
+
171
+ # Enhanced organization context for OAuth apps
172
+ default_org = None
173
+ if hasattr(token, 'application') and hasattr(token.application, 'oauth_app'):
174
+ # If this is an OAuth app token, use the app owner's organization
175
+ oauth_app = token.application.oauth_app
176
+ if hasattr(oauth_app, 'created_by') and oauth_app.created_by:
177
+ app_owner = oauth_app.created_by
178
+ if hasattr(app_owner, 'organizations'):
179
+ default_org = app_owner.organizations.filter(is_active=True).first()
180
+
181
+ request.org = SimpleLazyObject(
182
+ functools.partial(
183
+ get_request_org,
184
+ request,
185
+ user,
186
+ org_id=request.META.get("HTTP_X_ORG_ID"),
187
+ default_org=default_org,
188
+ )
189
+ )
190
+
191
+ return auth
192
+
193
+
194
+ class TwoFactorAuthenticationMiddleware(OTPMiddleware):
195
+ pass
196
+
197
+
198
+ class AccessMixin(mixins.AccessMixin):
199
+ """Verify that the current user is authenticated."""
200
+
201
+ def dispatch(self, request, *args, **kwargs):
202
+ if not hasattr(request, 'user') or request.user is None or not request.user.is_authenticated:
203
+ authenticate_user(request)
204
+
205
+ request.user = SimpleLazyObject(
206
+ functools.partial(get_request_user, request, request.user)
207
+ )
208
+
209
+ return super().dispatch(request, *args, **kwargs)
210
+
211
+
212
+ class AuthenticationMiddleware(BaseAuthenticationMiddleware):
213
+ def process_response(self, request, response):
214
+ if getattr(request, "org", None) is not None:
215
+ response.set_cookie("org_id", getattr(request.org, "id", None))
216
+ response["X-org-id"] = getattr(request.org, "id", None)
217
+
218
+ if getattr(request, "test_mode", None) is not None:
219
+ response.set_cookie("test_mode", request.test_mode)
220
+ response["X-test-mode"] = request.test_mode
221
+
222
+ return response
223
+
224
+ def process_request(self, request):
225
+ super().process_request(request)
226
+
227
+ request = authenticate_user(request)
228
+
229
+ if hasattr(request, "user") and getattr(request, "org", None) is None:
230
+ request.org = get_request_org(
231
+ request,
232
+ request.user,
233
+ org_id=request.META.get("HTTP_X_ORG_ID"),
234
+ )
235
+
236
+ if not hasattr(request, "test_mode"):
237
+ request.test_mode = get_request_test_mode(request)
238
+
239
+
240
+ def authenticate_user(request):
241
+ def authenticate(request, authenticator):
242
+ # Check if user exists and is not authenticated
243
+ if not hasattr(request, 'user') or request.user is None or not getattr(request.user, 'is_authenticated', False):
244
+ auth = pydoc.locate(authenticator)().authenticate(request)
245
+
246
+ if auth is not None:
247
+ user, token = auth
248
+ request.user = user
249
+ request.token = token
250
+
251
+ return request
252
+
253
+ try:
254
+ return functools.reduce(authenticate, AUTHENTICATION_CLASSES, request)
255
+ except Exception:
256
+ return request
257
+
258
+
259
+ def get_request_org(request, user, org_id: str = None, default_org=None):
260
+ """
261
+ Attempts to find and return an organization.
262
+ """
263
+ if settings.MULTI_ORGANIZATIONS:
264
+ try:
265
+ from karrio.server.orgs.models import Organization
266
+
267
+ if default_org is not None:
268
+ org = default_org
269
+ elif user and hasattr(user, 'id') and user.id:
270
+ orgs = Organization.objects.filter(users__id=user.id)
271
+ org = (
272
+ orgs.filter(id=org_id).first()
273
+ if org_id is not None and orgs.filter(id=org_id).exists()
274
+ else orgs.filter(is_active=True).first()
275
+ )
276
+ else:
277
+ org = None
278
+
279
+ if org is not None and not org.is_active:
280
+ raise exceptions.AuthenticationFailed(
281
+ _("Organization is inactive"), code="inactive_organization"
282
+ )
283
+
284
+ if org is None and org_id is not None:
285
+ raise exceptions.AuthenticationFailed(
286
+ _("No active organization found with the given credentials"),
287
+ code="invalid_organization",
288
+ )
289
+
290
+ return org
291
+ except ProgrammingError:
292
+ pass
293
+
294
+ return None
295
+
296
+
297
+ def get_request_user(request, user):
298
+ if not getattr(request, "otp_is_verified", True):
299
+ raise exceptions.AuthenticationFailed(
300
+ _("Authentication token not verified"), code="otp_not_verified"
301
+ )
302
+
303
+ if user is not None:
304
+ user.otp_device = None
305
+ user.is_verified = functools.partial(
306
+ lambda _: getattr(request, "otp_is_verified", True), user
307
+ )
308
+
309
+ return user
310
+
311
+
312
+ def get_request_test_mode(request):
313
+ return yaml.safe_load(request.META.get("HTTP_X_TEST_MODE", "")) or False
@@ -0,0 +1,12 @@
1
+ from karrio.server.conf import settings, DEFAULT_ALLOWED_CONFIG
2
+
3
+
4
+ TEMPLATE_SETTINGS_ACCESS_LIST = (
5
+ getattr(settings, "TEMPLATE_SETTINGS_ACCESS_LIST", None) or DEFAULT_ALLOWED_CONFIG
6
+ )
7
+
8
+
9
+ def get_settings(request):
10
+ return {
11
+ name: getattr(settings, name, None) for name in TEMPLATE_SETTINGS_ACCESS_LIST
12
+ }