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.
- karrio/server/conf.py +54 -0
- karrio/server/core/__init__.py +3 -0
- karrio/server/core/admin.py +1 -0
- karrio/server/core/apps.py +10 -0
- karrio/server/core/authentication.py +313 -0
- karrio/server/core/context_processors.py +12 -0
- karrio/server/core/datatypes.py +369 -0
- karrio/server/core/dataunits.py +156 -0
- karrio/server/core/exceptions.py +200 -0
- karrio/server/core/fields.py +12 -0
- karrio/server/core/filters.py +823 -0
- karrio/server/core/gateway.py +720 -0
- karrio/server/core/management/commands/cli.py +19 -0
- karrio/server/core/management/commands/create_oauth_client.py +41 -0
- karrio/server/core/middleware.py +95 -0
- karrio/server/core/migrations/0001_initial.py +28 -0
- karrio/server/core/migrations/0002_apilogindex.py +69 -0
- karrio/server/core/migrations/0003_apilogindex_test_mode.py +62 -0
- karrio/server/core/migrations/0004_metafield.py +74 -0
- karrio/server/core/migrations/0005_alter_metafield_type_alter_metafield_value.py +23 -0
- karrio/server/core/migrations/__init__.py +0 -0
- karrio/server/core/models/__init__.py +48 -0
- karrio/server/core/models/base.py +70 -0
- karrio/server/core/models/entity.py +22 -0
- karrio/server/core/models/metafield.py +144 -0
- karrio/server/core/models/third_party.py +21 -0
- karrio/server/core/oauth_validators.py +171 -0
- karrio/server/core/permissions.py +37 -0
- karrio/server/core/renderers.py +11 -0
- karrio/server/core/router.py +3 -0
- karrio/server/core/serializers.py +1898 -0
- karrio/server/core/signals.py +57 -0
- karrio/server/core/tests.py +98 -0
- karrio/server/core/urls.py +12 -0
- karrio/server/core/utils.py +479 -0
- karrio/server/core/validators.py +416 -0
- karrio/server/core/views/__init__.py +2 -0
- karrio/server/core/views/api.py +133 -0
- karrio/server/core/views/metadata.py +44 -0
- karrio/server/core/views/oauth.py +74 -0
- karrio/server/core/views/references.py +82 -0
- karrio/server/core/views/schema.py +310 -0
- karrio/server/filters/__init__.py +2 -0
- karrio/server/filters/abstract.py +26 -0
- karrio/server/iam/__init__.py +0 -0
- karrio/server/iam/admin.py +3 -0
- karrio/server/iam/apps.py +21 -0
- karrio/server/iam/migrations/0001_initial.py +33 -0
- karrio/server/iam/migrations/__init__.py +0 -0
- karrio/server/iam/models.py +48 -0
- karrio/server/iam/permissions.py +134 -0
- karrio/server/iam/serializers.py +39 -0
- karrio/server/iam/signals.py +20 -0
- karrio/server/iam/tests.py +3 -0
- karrio/server/iam/views.py +3 -0
- karrio/server/openapi.py +75 -0
- karrio/server/providers/__init__.py +1 -0
- karrio/server/providers/admin.py +364 -0
- karrio/server/providers/apps.py +10 -0
- karrio/server/providers/extension/__init__.py +1 -0
- karrio/server/providers/extension/models/__init__.py +1 -0
- karrio/server/providers/extension/models/allied_express.py +22 -0
- karrio/server/providers/extension/models/allied_express_local.py +22 -0
- karrio/server/providers/extension/models/amazon_shipping.py +27 -0
- karrio/server/providers/extension/models/aramex.py +25 -0
- karrio/server/providers/extension/models/asendia_us.py +21 -0
- karrio/server/providers/extension/models/australiapost.py +20 -0
- karrio/server/providers/extension/models/boxknight.py +19 -0
- karrio/server/providers/extension/models/bpost.py +21 -0
- karrio/server/providers/extension/models/canadapost.py +21 -0
- karrio/server/providers/extension/models/canpar.py +19 -0
- karrio/server/providers/extension/models/chronopost.py +22 -0
- karrio/server/providers/extension/models/colissimo.py +22 -0
- karrio/server/providers/extension/models/dhl_express.py +23 -0
- karrio/server/providers/extension/models/dhl_parcel_de.py +25 -0
- karrio/server/providers/extension/models/dhl_poland.py +22 -0
- karrio/server/providers/extension/models/dhl_universal.py +19 -0
- karrio/server/providers/extension/models/dicom.py +20 -0
- karrio/server/providers/extension/models/dpd.py +37 -0
- karrio/server/providers/extension/models/dpdhl.py +26 -0
- karrio/server/providers/extension/models/easypost.py +20 -0
- karrio/server/providers/extension/models/eshipper.py +21 -0
- karrio/server/providers/extension/models/fedex.py +25 -0
- karrio/server/providers/extension/models/fedex_ws.py +24 -0
- karrio/server/providers/extension/models/freightcom.py +21 -0
- karrio/server/providers/extension/models/generic.py +35 -0
- karrio/server/providers/extension/models/geodis.py +22 -0
- karrio/server/providers/extension/models/hay_post.py +22 -0
- karrio/server/providers/extension/models/laposte.py +19 -0
- karrio/server/providers/extension/models/locate2u.py +22 -0
- karrio/server/providers/extension/models/nationex.py +22 -0
- karrio/server/providers/extension/models/purolator.py +21 -0
- karrio/server/providers/extension/models/roadie.py +18 -0
- karrio/server/providers/extension/models/royalmail.py +19 -0
- karrio/server/providers/extension/models/sendle.py +22 -0
- karrio/server/providers/extension/models/tge.py +63 -0
- karrio/server/providers/extension/models/tnt.py +23 -0
- karrio/server/providers/extension/models/ups.py +23 -0
- karrio/server/providers/extension/models/usps.py +23 -0
- karrio/server/providers/extension/models/usps_international.py +23 -0
- karrio/server/providers/extension/models/usps_wt.py +24 -0
- karrio/server/providers/extension/models/usps_wt_international.py +24 -0
- karrio/server/providers/extension/models/zoom2u.py +23 -0
- karrio/server/providers/migrations/0001_initial.py +140 -0
- karrio/server/providers/migrations/0002_carrier_active.py +18 -0
- karrio/server/providers/migrations/0003_auto_20201230_0820.py +24 -0
- karrio/server/providers/migrations/0004_auto_20210212_0554.py +178 -0
- karrio/server/providers/migrations/0005_auto_20210212_0555.py +18 -0
- karrio/server/providers/migrations/0006_australiapostsettings.py +29 -0
- karrio/server/providers/migrations/0007_auto_20210213_0206.py +21 -0
- karrio/server/providers/migrations/0008_auto_20210214_0409.py +30 -0
- karrio/server/providers/migrations/0009_auto_20210308_0302.py +18 -0
- karrio/server/providers/migrations/0010_auto_20210409_0852.py +32 -0
- karrio/server/providers/migrations/0011_auto_20210409_0853.py +21 -0
- karrio/server/providers/migrations/0012_alter_carrier_options.py +17 -0
- karrio/server/providers/migrations/0013_tntsettings.py +30 -0
- karrio/server/providers/migrations/0014_auto_20210612_1608.py +46 -0
- karrio/server/providers/migrations/0015_auto_20210615_1601.py +28 -0
- karrio/server/providers/migrations/0016_alter_purolatorsettings_user_token.py +18 -0
- karrio/server/providers/migrations/0017_auto_20210805_0359.py +1293 -0
- karrio/server/providers/migrations/0018_alter_fedexsettings_user_key.py +18 -0
- karrio/server/providers/migrations/0019_dhlpolandsettings_servicelevel.py +65 -0
- karrio/server/providers/migrations/0020_genericsettings_labeltemplate.py +52 -0
- karrio/server/providers/migrations/0021_auto_20211231_2353.py +40 -0
- karrio/server/providers/migrations/0022_carrier_metadata.py +18 -0
- karrio/server/providers/migrations/0023_auto_20220124_1916.py +27 -0
- karrio/server/providers/migrations/0024_alter_genericsettings_custom_carrier_name.py +19 -0
- karrio/server/providers/migrations/0025_alter_servicelevel_service_code.py +19 -0
- karrio/server/providers/migrations/0026_auto_20220208_0132.py +59 -0
- karrio/server/providers/migrations/0027_auto_20220304_1340.py +29 -0
- karrio/server/providers/migrations/0028_auto_20220323_1500.py +33 -0
- karrio/server/providers/migrations/0029_easypostsettings.py +27 -0
- karrio/server/providers/migrations/0030_amazonmwssettings.py +29 -0
- karrio/server/providers/migrations/0031_delete_amazonmwssettings.py +18 -0
- karrio/server/providers/migrations/0032_alter_carrier_test.py +18 -0
- karrio/server/providers/migrations/0033_auto_20220708_1350.py +22 -0
- karrio/server/providers/migrations/0034_amazonmwssettings_dpdhlsettings.py +47 -0
- karrio/server/providers/migrations/0035_alter_carrier_capabilities.py +43 -0
- karrio/server/providers/migrations/0036_upsfreightsettings.py +31 -0
- karrio/server/providers/migrations/0037_chronopostsettings.py +29 -0
- karrio/server/providers/migrations/0038_alter_genericsettings_label_template.py +19 -0
- karrio/server/providers/migrations/0039_auto_20220906_0612.py +23 -0
- karrio/server/providers/migrations/0040_dpdhlsettings_services.py +18 -0
- karrio/server/providers/migrations/0041_auto_20221105_0705.py +38 -0
- karrio/server/providers/migrations/0042_auto_20221215_1642.py +23 -0
- karrio/server/providers/migrations/0043_alter_genericsettings_account_number_and_more.py +39 -0
- karrio/server/providers/migrations/0044_carrier_carrier_capabilities.py +64 -0
- karrio/server/providers/migrations/0045_alter_carrier_active_alter_carrier_carrier_id.py +31 -0
- karrio/server/providers/migrations/0046_remove_dpdhlsettings_signature_and_more.py +41 -0
- karrio/server/providers/migrations/0047_dpdsettings.py +286 -0
- karrio/server/providers/migrations/0048_servicelevel_min_weight_servicelevel_transit_days_and_more.py +64 -0
- karrio/server/providers/migrations/0049_boxknightsettings_geodissettings_lapostesettings_and_more.py +156 -0
- karrio/server/providers/migrations/0050_carrier_is_system_alter_carrier_metadata_and_more.py +106 -0
- karrio/server/providers/migrations/0051_rename_username_upssettings_client_id_and_more.py +31 -0
- karrio/server/providers/migrations/0052_alter_upssettings_account_number_and_more.py +20 -0
- karrio/server/providers/migrations/0053_locate2usettings.py +281 -0
- karrio/server/providers/migrations/0054_zoom2usettings.py +280 -0
- karrio/server/providers/migrations/0055_rename_amazonmwssettings_amazonshippingsettings_and_more.py +44 -0
- karrio/server/providers/migrations/0056_asendiaussettings_geodissettings_code_client_and_more.py +75 -0
- karrio/server/providers/migrations/0057_alter_servicelevel_weight_unit_belgianpostsettings.py +51 -0
- karrio/server/providers/migrations/0058_alliedexpresssettings.py +38 -0
- karrio/server/providers/migrations/0059_ratesheet.py +81 -0
- karrio/server/providers/migrations/0060_belgianpostsettings_rate_sheet_and_more.py +73 -0
- karrio/server/providers/migrations/0061_alliedexpresssettings_service_type.py +17 -0
- karrio/server/providers/migrations/0062_sendlesettings_account_country_code.py +257 -0
- karrio/server/providers/migrations/0063_servicelevel_metadata.py +25 -0
- karrio/server/providers/migrations/0064_alliedexpresslocalsettings.py +43 -0
- karrio/server/providers/migrations/0065_servicelevel_carrier_service_code_and_more.py +66 -0
- karrio/server/providers/migrations/0066_rename_fedexsettings_fedexwssettings_and_more.py +28 -0
- karrio/server/providers/migrations/0067_fedexsettings.py +283 -0
- karrio/server/providers/migrations/0068_fedexsettings_track_api_key_and_more.py +38 -0
- karrio/server/providers/migrations/0069_alter_canadapostsettings_contract_id_and_more.py +23 -0
- karrio/server/providers/migrations/0070_tgesettings_alter_carrier_capabilities.py +65 -0
- karrio/server/providers/migrations/0071_alter_tgesettings_my_toll_token.py +18 -0
- karrio/server/providers/migrations/0072_rename_eshippersettings_eshipperxmlsettings_and_more.py +28 -0
- karrio/server/providers/migrations/0073_delete_eshipperxmlsettings.py +41 -0
- karrio/server/providers/migrations/0074_eshippersettings.py +38 -0
- karrio/server/providers/migrations/0075_haypostsettings.py +40 -0
- karrio/server/providers/migrations/0076_rename_customer_registration_id_uspsinternationalsettings_account_number_and_more.py +125 -0
- karrio/server/providers/migrations/0077_uspswtinternationalsettings_uspswtsettings_and_more.py +165 -0
- karrio/server/providers/migrations/0078_auto_20240813_1552.py +120 -0
- karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py +31 -0
- karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py +3025 -0
- karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py +338 -0
- karrio/server/providers/migrations/__init__.py +0 -0
- karrio/server/providers/models/__init__.py +17 -0
- karrio/server/providers/models/carrier.py +309 -0
- karrio/server/providers/models/config.py +30 -0
- karrio/server/providers/models/service.py +62 -0
- karrio/server/providers/models/sheet.py +60 -0
- karrio/server/providers/models/template.py +39 -0
- karrio/server/providers/models/utils.py +58 -0
- karrio/server/providers/router.py +3 -0
- karrio/server/providers/serializers/__init__.py +3 -0
- karrio/server/providers/serializers/base.py +277 -0
- karrio/server/providers/signals.py +27 -0
- karrio/server/providers/tests.py +3 -0
- karrio/server/providers/urls.py +11 -0
- karrio/server/providers/views/__init__.py +0 -0
- karrio/server/providers/views/carriers.py +269 -0
- karrio/server/providers/views/connections.py +176 -0
- karrio/server/samples.py +352 -0
- karrio/server/serializers/__init__.py +2 -0
- karrio/server/serializers/abstract.py +506 -0
- karrio/server/tracing/__init__.py +0 -0
- karrio/server/tracing/admin.py +63 -0
- karrio/server/tracing/apps.py +8 -0
- karrio/server/tracing/migrations/0001_initial.py +41 -0
- karrio/server/tracing/migrations/0002_auto_20220710_1307.py +22 -0
- karrio/server/tracing/migrations/0003_auto_20221105_0317.py +43 -0
- karrio/server/tracing/migrations/0004_tracingrecord_carrier_account_idx.py +24 -0
- karrio/server/tracing/migrations/0005_optimise_tracingrecord_request_log_idx.py +25 -0
- karrio/server/tracing/migrations/0006_alter_tracingrecord_options_and_more.py +49 -0
- karrio/server/tracing/migrations/__init__.py +0 -0
- karrio/server/tracing/models.py +80 -0
- karrio/server/tracing/tests.py +3 -0
- karrio/server/tracing/utils.py +112 -0
- karrio/server/user/__init__.py +0 -0
- karrio/server/user/admin.py +96 -0
- karrio/server/user/apps.py +7 -0
- karrio/server/user/forms.py +35 -0
- karrio/server/user/migrations/0001_initial.py +41 -0
- karrio/server/user/migrations/0002_token.py +29 -0
- karrio/server/user/migrations/0003_token_test_mode.py +20 -0
- karrio/server/user/migrations/0004_group.py +26 -0
- karrio/server/user/migrations/0005_token_label.py +21 -0
- karrio/server/user/migrations/0006_workspaceconfig.py +63 -0
- karrio/server/user/migrations/__init__.py +0 -0
- karrio/server/user/models.py +203 -0
- karrio/server/user/serializers.py +46 -0
- karrio/server/user/templates/registration/login.html +108 -0
- karrio/server/user/templates/registration/registration_confirm_email.html +10 -0
- karrio/server/user/templates/registration/registration_confirm_email.txt +3 -0
- karrio/server/user/tests.py +3 -0
- karrio/server/user/urls.py +10 -0
- karrio/server/user/utils.py +60 -0
- karrio/server/user/views.py +9 -0
- karrio_server_core-2025.5rc1.dist-info/METADATA +32 -0
- karrio_server_core-2025.5rc1.dist-info/RECORD +241 -0
- karrio_server_core-2025.5rc1.dist-info/WHEEL +5 -0
- 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
|