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
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 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
@@ -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
|
+
}
|