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
@@ -0,0 +1,144 @@
1
+ import json
2
+ import re
3
+ import functools
4
+ from datetime import datetime, date
5
+ import django.conf as conf
6
+ import django.db.models as models
7
+ import django.utils.translation as translation
8
+ from django.core.exceptions import ValidationError
9
+
10
+ import karrio.server.core.models.base as core
11
+ import karrio.server.core.models.entity as entity
12
+
13
+ _ = translation.gettext_lazy
14
+
15
+
16
+ @core.register_model
17
+ class Metafield(entity.OwnedEntity):
18
+ class Meta:
19
+ db_table = "metafield"
20
+ verbose_name = "Metafield"
21
+ verbose_name_plural = "Metafields"
22
+
23
+ id = models.CharField(
24
+ max_length=50,
25
+ editable=False,
26
+ primary_key=True,
27
+ default=functools.partial(core.uuid, prefix="metaf_"),
28
+ )
29
+ key = models.CharField(_("name"), max_length=50, db_index=True)
30
+ value = models.JSONField(null=True, blank=True)
31
+ type = models.CharField(
32
+ _("type"),
33
+ max_length=50,
34
+ choices=core.METAFIELD_TYPE,
35
+ default=core.METAFIELD_TYPE[0][0],
36
+ db_index=True,
37
+ )
38
+ is_required = models.BooleanField(null=False, default=False)
39
+
40
+ # Related fields
41
+ created_by = models.ForeignKey(
42
+ conf.settings.AUTH_USER_MODEL,
43
+ blank=True,
44
+ null=True,
45
+ on_delete=models.CASCADE,
46
+ editable=False,
47
+ )
48
+
49
+ @property
50
+ def object_type(self):
51
+ return "metafield"
52
+
53
+ def clean(self):
54
+ """Validate the value based on the metafield type."""
55
+ super().clean()
56
+ if self.value is not None:
57
+ self._validate_value()
58
+
59
+ def _validate_value(self):
60
+ """Validate value based on metafield type."""
61
+ if self.type == core.MetafieldType.text:
62
+ if not isinstance(self.value, str):
63
+ raise ValidationError(f"Value must be a string for type 'text'")
64
+
65
+ elif self.type == core.MetafieldType.number:
66
+ if not isinstance(self.value, (int, float)):
67
+ raise ValidationError(f"Value must be a number for type 'number'")
68
+
69
+ elif self.type == core.MetafieldType.boolean:
70
+ if not isinstance(self.value, bool):
71
+ raise ValidationError(f"Value must be a boolean for type 'boolean'")
72
+
73
+ elif self.type == core.MetafieldType.json:
74
+ # JSON can be any valid JSON value (dict, list, string, number, boolean, null)
75
+ try:
76
+ if isinstance(self.value, str):
77
+ json.loads(self.value)
78
+ except (json.JSONDecodeError, TypeError):
79
+ raise ValidationError(f"Value must be valid JSON for type 'json'")
80
+
81
+ elif self.type == core.MetafieldType.date:
82
+ if isinstance(self.value, str):
83
+ try:
84
+ datetime.strptime(self.value, '%Y-%m-%d')
85
+ except ValueError:
86
+ raise ValidationError(f"Value must be a valid date (YYYY-MM-DD) for type 'date'")
87
+ else:
88
+ raise ValidationError(f"Value must be a date string (YYYY-MM-DD) for type 'date'")
89
+
90
+ elif self.type == core.MetafieldType.date_time:
91
+ if isinstance(self.value, str):
92
+ try:
93
+ datetime.fromisoformat(self.value.replace('Z', '+00:00'))
94
+ except ValueError:
95
+ raise ValidationError(f"Value must be a valid ISO datetime for type 'date_time'")
96
+ else:
97
+ raise ValidationError(f"Value must be a datetime string for type 'date_time'")
98
+
99
+ elif self.type == core.MetafieldType.password:
100
+ if not isinstance(self.value, str):
101
+ raise ValidationError(f"Value must be a string for type 'password'")
102
+
103
+ def get_parsed_value(self):
104
+ """Return the value parsed according to its type."""
105
+ if self.value is None:
106
+ return None
107
+
108
+ if self.type == core.MetafieldType.text:
109
+ return str(self.value)
110
+
111
+ elif self.type == core.MetafieldType.number:
112
+ return self.value
113
+
114
+ elif self.type == core.MetafieldType.boolean:
115
+ return bool(self.value)
116
+
117
+ elif self.type == core.MetafieldType.json:
118
+ if isinstance(self.value, str):
119
+ try:
120
+ return json.loads(self.value)
121
+ except json.JSONDecodeError:
122
+ return self.value
123
+ return self.value
124
+
125
+ elif self.type == core.MetafieldType.date:
126
+ if isinstance(self.value, str):
127
+ try:
128
+ return datetime.strptime(self.value, '%Y-%m-%d').date()
129
+ except ValueError:
130
+ return self.value
131
+ return self.value
132
+
133
+ elif self.type == core.MetafieldType.date_time:
134
+ if isinstance(self.value, str):
135
+ try:
136
+ return datetime.fromisoformat(self.value.replace('Z', '+00:00'))
137
+ except ValueError:
138
+ return self.value
139
+ return self.value
140
+
141
+ elif self.type == core.MetafieldType.password:
142
+ return str(self.value)
143
+
144
+ return self.value
@@ -0,0 +1,21 @@
1
+ from django.db import models
2
+ from rest_framework_tracking.models import APIRequestLog
3
+
4
+ from karrio.server.core.models.base import ControlledAccessModel
5
+
6
+
7
+ class APILog(APIRequestLog, ControlledAccessModel):
8
+ class Meta:
9
+ ordering = ["-requested_at"]
10
+ proxy = True
11
+
12
+ @property
13
+ def object_type(self):
14
+ return "log"
15
+
16
+
17
+ class APILogIndex(APILog):
18
+ entity_id = models.CharField(max_length=50, null=True, db_index=True)
19
+ test_mode = models.BooleanField(
20
+ default=True, null=True, help_text="execution context"
21
+ )
@@ -0,0 +1,171 @@
1
+ import hashlib
2
+ import base64
3
+ from oauth2_provider.oauth2_validators import OAuth2Validator
4
+ from django.contrib.auth import get_user_model
5
+
6
+
7
+ class CustomOAuth2Validator(OAuth2Validator):
8
+ oidc_claim_scope = None
9
+
10
+ def get_additional_claims(self):
11
+ return {
12
+ "name": lambda request: getattr(request.user, 'full_name', '') if request.user else '',
13
+ "email": lambda request: getattr(request.user, 'email', '') if request.user else '',
14
+ }
15
+
16
+ def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
17
+ """
18
+ Validate grant type with proper format conversion.
19
+
20
+ django-oauth-toolkit stores grant types with hyphens (e.g., 'authorization-code')
21
+ but OAuth2 spec uses underscores (e.g., 'authorization_code').
22
+ This method handles the conversion.
23
+ """
24
+ # Convert OAuth2 spec format to django-oauth-toolkit format
25
+ grant_type_mapping = {
26
+ 'authorization_code': 'authorization-code',
27
+ 'client_credentials': 'client-credentials',
28
+ 'refresh_token': 'refresh-token',
29
+ 'password': 'password',
30
+ }
31
+
32
+ # Get the stored grant type format
33
+ stored_grant_type = grant_type_mapping.get(grant_type, grant_type)
34
+
35
+ # Check if the client supports this grant type
36
+ if client and hasattr(client, 'authorization_grant_type'):
37
+ is_valid = client.authorization_grant_type == stored_grant_type
38
+ if is_valid:
39
+ return True
40
+
41
+ # Fall back to parent implementation
42
+ return super().validate_grant_type(client_id, grant_type, client, request, *args, **kwargs)
43
+
44
+ def validate_code(self, client_id, code, client, request, *args, **kwargs):
45
+ """
46
+ Override validate_code to ensure proper grant type handling for authorization code flow.
47
+ """
48
+ # First validate the code using parent implementation
49
+ is_valid = super().validate_code(client_id, code, client, request, *args, **kwargs)
50
+
51
+ if is_valid and client:
52
+ # Ensure the client supports authorization code flow
53
+ # Convert the request grant type to stored format for comparison
54
+ if request.grant_type == 'authorization_code':
55
+ return client.authorization_grant_type == 'authorization-code'
56
+
57
+ return is_valid
58
+
59
+ def validate_client_id(self, client_id, request, *args, **kwargs):
60
+ """
61
+ Validate the client_id and set the user context for different flows.
62
+ """
63
+ is_valid = super().validate_client_id(client_id, request, *args, **kwargs)
64
+
65
+ if is_valid:
66
+ try:
67
+ from oauth2_provider.models import Application
68
+ application = Application.objects.get(client_id=client_id)
69
+
70
+ # Set application on request for later use
71
+ request.oauth_application = application
72
+
73
+ # For client credentials flow, set the user from the OAuth application owner
74
+ if request.grant_type == 'client_credentials' and application.user:
75
+ request.user = application.user
76
+
77
+ except Application.DoesNotExist:
78
+ pass
79
+
80
+ return is_valid
81
+
82
+ def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
83
+ """
84
+ Validate redirect URI for Authorization Code Flow with enhanced security.
85
+ """
86
+ is_valid = super().validate_redirect_uri(client_id, redirect_uri, request, *args, **kwargs)
87
+
88
+ if is_valid and redirect_uri:
89
+ # Additional security: ensure HTTPS in production
90
+ from django.conf import settings
91
+ if not settings.DEBUG and not redirect_uri.startswith('https://'):
92
+ return False
93
+
94
+ return is_valid
95
+
96
+ def validate_code_challenge(self, challenge, request, *args, **kwargs):
97
+ """
98
+ Validate PKCE code challenge for enhanced security.
99
+ """
100
+ # This is called when PKCE is enabled
101
+ if challenge:
102
+ # Validate that the challenge is base64url encoded and has proper length
103
+ try:
104
+ decoded = base64.urlsafe_b64decode(challenge + '==') # Add padding
105
+ return len(decoded) >= 32 # At least 256 bits
106
+ except Exception:
107
+ return False
108
+
109
+ # If PKCE is required but no challenge provided, reject
110
+ from django.conf import settings
111
+ oauth_settings = getattr(settings, 'OAUTH2_PROVIDER', {})
112
+ if oauth_settings.get('PKCE_REQUIRED', False):
113
+ return False
114
+
115
+ return True
116
+
117
+ def validate_code_verifier(self, verifier, challenge, challenge_method, request, *args, **kwargs):
118
+ """
119
+ Validate PKCE code verifier against the challenge.
120
+ """
121
+ if challenge_method == 'S256':
122
+ # SHA256 challenge method
123
+ verifier_hash = hashlib.sha256(verifier.encode('ascii')).digest()
124
+ verifier_challenge = base64.urlsafe_b64encode(verifier_hash).decode('ascii').rstrip('=')
125
+ return verifier_challenge == challenge
126
+ elif challenge_method == 'plain':
127
+ # Plain text challenge method (less secure, but allowed)
128
+ return verifier == challenge
129
+
130
+ return False
131
+
132
+ def get_default_scopes(self, client_id, request, *args, **kwargs):
133
+ """
134
+ Return default scopes for the application.
135
+ """
136
+ try:
137
+ from oauth2_provider.models import Application
138
+ application = Application.objects.get(client_id=client_id)
139
+
140
+ # For Karrio apps, default to read scope
141
+ if hasattr(application, 'oauth_app'):
142
+ return ['read']
143
+
144
+ except Application.DoesNotExist:
145
+ pass
146
+
147
+ return super().get_default_scopes(client_id, request, *args, **kwargs)
148
+
149
+ def save_authorization_code(self, client_id, code, request, *args, **kwargs):
150
+ """
151
+ Store authorization code with additional security measures.
152
+ """
153
+ # Store the authorization code with enhanced security
154
+ super().save_authorization_code(client_id, code, request, *args, **kwargs)
155
+
156
+ # Log OAuth events for audit purposes
157
+ import logging
158
+ logger = logging.getLogger('karrio.server.oauth')
159
+ logger.info(f"Authorization code granted for client {client_id}")
160
+
161
+ def authenticate_user(self, request):
162
+ """
163
+ Authenticate user for Authorization Code Flow.
164
+ """
165
+ user = super().authenticate_user(request)
166
+
167
+ if user:
168
+ # Set additional user context
169
+ request.oauth_user = user
170
+
171
+ return user
@@ -0,0 +1,37 @@
1
+ import logging
2
+ import pydoc
3
+ import typing
4
+ from rest_framework import permissions, exceptions
5
+
6
+ import karrio.server.conf as conf
7
+
8
+ logger = logging.getLogger(__name__)
9
+ PERMISSION_CHECKS = getattr(
10
+ conf.settings, "PERMISSION_CHECKS", ["karrio.server.core.permissions.check_feature_flags"]
11
+ )
12
+
13
+
14
+ class AllowEnabledAPI(permissions.BasePermission):
15
+ """
16
+ Global permission check for blocked IPs.
17
+ """
18
+
19
+ def has_permission(self, request, view):
20
+ if ("/v1/data" in request.path) and (conf.settings.DATA_IMPORT_EXPORT is False):
21
+ raise exceptions.PermissionDenied()
22
+
23
+ if ("/v1/orders" in request.path) and (conf.settings.ORDERS_MANAGEMENT is False):
24
+ raise exceptions.PermissionDenied()
25
+
26
+ return super().has_permission(request, view)
27
+
28
+
29
+ def check_permissions(context, keys: typing.List[str]):
30
+ for check in PERMISSION_CHECKS:
31
+ pydoc.locate(check)(context=context, keys=keys) # type: ignore
32
+
33
+
34
+ def check_feature_flags(keys: typing.List[str] = [], **kwargs):
35
+ flags = [flag for flag in keys if flag in conf.FEATURE_FLAGS]
36
+ if any([conf.settings.get(flag) is False for flag in flags]):
37
+ raise exceptions.PermissionDenied()
@@ -0,0 +1,11 @@
1
+ from rest_framework.renderers import BaseRenderer
2
+
3
+
4
+ class BinaryFileRenderer(BaseRenderer):
5
+ media_type = 'application/octet-stream'
6
+ format = None
7
+ charset = None
8
+ render_style = 'binary'
9
+
10
+ def render(self, data, media_type=None, renderer_context=None):
11
+ return data
@@ -0,0 +1,3 @@
1
+ from rest_framework.routers import DefaultRouter
2
+
3
+ router = DefaultRouter(trailing_slash=False)