karrio-server-core 2025.5__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.
Files changed (213) 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 +347 -0
  6. karrio/server/core/config.py +31 -0
  7. karrio/server/core/context_processors.py +12 -0
  8. karrio/server/core/datatypes.py +394 -0
  9. karrio/server/core/dataunits.py +187 -0
  10. karrio/server/core/exceptions.py +404 -0
  11. karrio/server/core/fields.py +12 -0
  12. karrio/server/core/filters.py +837 -0
  13. karrio/server/core/gateway.py +1011 -0
  14. karrio/server/core/logging.py +403 -0
  15. karrio/server/core/management/commands/cli.py +19 -0
  16. karrio/server/core/management/commands/create_oauth_client.py +41 -0
  17. karrio/server/core/management/commands/runserver.py +5 -0
  18. karrio/server/core/middleware.py +197 -0
  19. karrio/server/core/migrations/0001_initial.py +28 -0
  20. karrio/server/core/migrations/0002_apilogindex.py +69 -0
  21. karrio/server/core/migrations/0003_apilogindex_test_mode.py +62 -0
  22. karrio/server/core/migrations/0004_metafield.py +74 -0
  23. karrio/server/core/migrations/0005_alter_metafield_type_alter_metafield_value.py +23 -0
  24. karrio/server/core/migrations/0006_add_api_log_requested_at_index.py +22 -0
  25. karrio/server/core/migrations/__init__.py +0 -0
  26. karrio/server/core/models/__init__.py +48 -0
  27. karrio/server/core/models/base.py +103 -0
  28. karrio/server/core/models/entity.py +24 -0
  29. karrio/server/core/models/metafield.py +144 -0
  30. karrio/server/core/models/third_party.py +21 -0
  31. karrio/server/core/oauth_validators.py +170 -0
  32. karrio/server/core/permissions.py +36 -0
  33. karrio/server/core/renderers.py +11 -0
  34. karrio/server/core/router.py +3 -0
  35. karrio/server/core/serializers.py +1971 -0
  36. karrio/server/core/signals.py +55 -0
  37. karrio/server/core/telemetry.py +573 -0
  38. karrio/server/core/tests.py +99 -0
  39. karrio/server/core/tests_resource_token.py +411 -0
  40. karrio/server/core/urls.py +12 -0
  41. karrio/server/core/utils.py +1025 -0
  42. karrio/server/core/validators.py +264 -0
  43. karrio/server/core/views/__init__.py +2 -0
  44. karrio/server/core/views/api.py +133 -0
  45. karrio/server/core/views/metadata.py +44 -0
  46. karrio/server/core/views/oauth.py +75 -0
  47. karrio/server/core/views/references.py +82 -0
  48. karrio/server/core/views/schema.py +310 -0
  49. karrio/server/filters/__init__.py +2 -0
  50. karrio/server/filters/abstract.py +26 -0
  51. karrio/server/iam/__init__.py +0 -0
  52. karrio/server/iam/admin.py +3 -0
  53. karrio/server/iam/apps.py +21 -0
  54. karrio/server/iam/migrations/0001_initial.py +33 -0
  55. karrio/server/iam/migrations/__init__.py +0 -0
  56. karrio/server/iam/models.py +48 -0
  57. karrio/server/iam/permissions.py +155 -0
  58. karrio/server/iam/serializers.py +54 -0
  59. karrio/server/iam/signals.py +18 -0
  60. karrio/server/iam/tests.py +3 -0
  61. karrio/server/iam/views.py +3 -0
  62. karrio/server/openapi.py +75 -0
  63. karrio/server/providers/__init__.py +1 -0
  64. karrio/server/providers/admin.py +364 -0
  65. karrio/server/providers/apps.py +10 -0
  66. karrio/server/providers/management/commands/migrate_rate_sheets.py +101 -0
  67. karrio/server/providers/migrations/0001_initial.py +140 -0
  68. karrio/server/providers/migrations/0002_carrier_active.py +18 -0
  69. karrio/server/providers/migrations/0003_auto_20201230_0820.py +24 -0
  70. karrio/server/providers/migrations/0004_auto_20210212_0554.py +178 -0
  71. karrio/server/providers/migrations/0005_auto_20210212_0555.py +18 -0
  72. karrio/server/providers/migrations/0006_australiapostsettings.py +29 -0
  73. karrio/server/providers/migrations/0007_auto_20210213_0206.py +21 -0
  74. karrio/server/providers/migrations/0008_auto_20210214_0409.py +30 -0
  75. karrio/server/providers/migrations/0009_auto_20210308_0302.py +18 -0
  76. karrio/server/providers/migrations/0010_auto_20210409_0852.py +32 -0
  77. karrio/server/providers/migrations/0011_auto_20210409_0853.py +21 -0
  78. karrio/server/providers/migrations/0012_alter_carrier_options.py +17 -0
  79. karrio/server/providers/migrations/0013_tntsettings.py +30 -0
  80. karrio/server/providers/migrations/0014_auto_20210612_1608.py +46 -0
  81. karrio/server/providers/migrations/0015_auto_20210615_1601.py +28 -0
  82. karrio/server/providers/migrations/0016_alter_purolatorsettings_user_token.py +18 -0
  83. karrio/server/providers/migrations/0017_auto_20210805_0359.py +1293 -0
  84. karrio/server/providers/migrations/0018_alter_fedexsettings_user_key.py +18 -0
  85. karrio/server/providers/migrations/0019_dhlpolandsettings_servicelevel.py +65 -0
  86. karrio/server/providers/migrations/0020_genericsettings_labeltemplate.py +52 -0
  87. karrio/server/providers/migrations/0021_auto_20211231_2353.py +40 -0
  88. karrio/server/providers/migrations/0022_carrier_metadata.py +18 -0
  89. karrio/server/providers/migrations/0023_auto_20220124_1916.py +27 -0
  90. karrio/server/providers/migrations/0024_alter_genericsettings_custom_carrier_name.py +19 -0
  91. karrio/server/providers/migrations/0025_alter_servicelevel_service_code.py +19 -0
  92. karrio/server/providers/migrations/0026_auto_20220208_0132.py +59 -0
  93. karrio/server/providers/migrations/0027_auto_20220304_1340.py +29 -0
  94. karrio/server/providers/migrations/0028_auto_20220323_1500.py +33 -0
  95. karrio/server/providers/migrations/0029_easypostsettings.py +27 -0
  96. karrio/server/providers/migrations/0030_amazonmwssettings.py +29 -0
  97. karrio/server/providers/migrations/0031_delete_amazonmwssettings.py +18 -0
  98. karrio/server/providers/migrations/0032_alter_carrier_test.py +18 -0
  99. karrio/server/providers/migrations/0033_auto_20220708_1350.py +22 -0
  100. karrio/server/providers/migrations/0034_amazonmwssettings_dpdhlsettings.py +47 -0
  101. karrio/server/providers/migrations/0035_alter_carrier_capabilities.py +43 -0
  102. karrio/server/providers/migrations/0036_upsfreightsettings.py +31 -0
  103. karrio/server/providers/migrations/0037_chronopostsettings.py +29 -0
  104. karrio/server/providers/migrations/0038_alter_genericsettings_label_template.py +19 -0
  105. karrio/server/providers/migrations/0039_auto_20220906_0612.py +23 -0
  106. karrio/server/providers/migrations/0040_dpdhlsettings_services.py +18 -0
  107. karrio/server/providers/migrations/0041_auto_20221105_0705.py +38 -0
  108. karrio/server/providers/migrations/0042_auto_20221215_1642.py +23 -0
  109. karrio/server/providers/migrations/0043_alter_genericsettings_account_number_and_more.py +39 -0
  110. karrio/server/providers/migrations/0044_carrier_carrier_capabilities.py +64 -0
  111. karrio/server/providers/migrations/0045_alter_carrier_active_alter_carrier_carrier_id.py +31 -0
  112. karrio/server/providers/migrations/0046_remove_dpdhlsettings_signature_and_more.py +41 -0
  113. karrio/server/providers/migrations/0047_dpdsettings.py +286 -0
  114. karrio/server/providers/migrations/0048_servicelevel_min_weight_servicelevel_transit_days_and_more.py +64 -0
  115. karrio/server/providers/migrations/0049_boxknightsettings_geodissettings_lapostesettings_and_more.py +156 -0
  116. karrio/server/providers/migrations/0050_carrier_is_system_alter_carrier_metadata_and_more.py +106 -0
  117. karrio/server/providers/migrations/0051_rename_username_upssettings_client_id_and_more.py +31 -0
  118. karrio/server/providers/migrations/0052_alter_upssettings_account_number_and_more.py +20 -0
  119. karrio/server/providers/migrations/0053_locate2usettings.py +281 -0
  120. karrio/server/providers/migrations/0054_zoom2usettings.py +280 -0
  121. karrio/server/providers/migrations/0055_rename_amazonmwssettings_amazonshippingsettings_and_more.py +44 -0
  122. karrio/server/providers/migrations/0056_asendiaussettings_geodissettings_code_client_and_more.py +75 -0
  123. karrio/server/providers/migrations/0057_alter_servicelevel_weight_unit_belgianpostsettings.py +51 -0
  124. karrio/server/providers/migrations/0058_alliedexpresssettings.py +38 -0
  125. karrio/server/providers/migrations/0059_ratesheet.py +81 -0
  126. karrio/server/providers/migrations/0060_belgianpostsettings_rate_sheet_and_more.py +73 -0
  127. karrio/server/providers/migrations/0061_alliedexpresssettings_service_type.py +17 -0
  128. karrio/server/providers/migrations/0062_sendlesettings_account_country_code.py +257 -0
  129. karrio/server/providers/migrations/0063_servicelevel_metadata.py +25 -0
  130. karrio/server/providers/migrations/0064_alliedexpresslocalsettings.py +43 -0
  131. karrio/server/providers/migrations/0065_servicelevel_carrier_service_code_and_more.py +66 -0
  132. karrio/server/providers/migrations/0066_rename_fedexsettings_fedexwssettings_and_more.py +28 -0
  133. karrio/server/providers/migrations/0067_fedexsettings.py +283 -0
  134. karrio/server/providers/migrations/0068_fedexsettings_track_api_key_and_more.py +38 -0
  135. karrio/server/providers/migrations/0069_alter_canadapostsettings_contract_id_and_more.py +23 -0
  136. karrio/server/providers/migrations/0070_tgesettings_alter_carrier_capabilities.py +65 -0
  137. karrio/server/providers/migrations/0071_alter_tgesettings_my_toll_token.py +18 -0
  138. karrio/server/providers/migrations/0072_rename_eshippersettings_eshipperxmlsettings_and_more.py +28 -0
  139. karrio/server/providers/migrations/0073_delete_eshipperxmlsettings.py +41 -0
  140. karrio/server/providers/migrations/0074_eshippersettings.py +38 -0
  141. karrio/server/providers/migrations/0075_haypostsettings.py +40 -0
  142. karrio/server/providers/migrations/0076_rename_customer_registration_id_uspsinternationalsettings_account_number_and_more.py +125 -0
  143. karrio/server/providers/migrations/0077_uspswtinternationalsettings_uspswtsettings_and_more.py +165 -0
  144. karrio/server/providers/migrations/0078_auto_20240813_1552.py +120 -0
  145. karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py +31 -0
  146. karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py +3025 -0
  147. karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py +338 -0
  148. karrio/server/providers/migrations/0082_add_zone_identifiers.py +50 -0
  149. karrio/server/providers/migrations/0083_add_optimized_rate_sheet_structure.py +33 -0
  150. karrio/server/providers/migrations/0084_alter_servicelevel_currency.py +168 -0
  151. karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
  152. karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
  153. karrio/server/providers/migrations/__init__.py +0 -0
  154. karrio/server/providers/models/__init__.py +16 -0
  155. karrio/server/providers/models/carrier.py +387 -0
  156. karrio/server/providers/models/config.py +30 -0
  157. karrio/server/providers/models/service.py +192 -0
  158. karrio/server/providers/models/sheet.py +287 -0
  159. karrio/server/providers/models/template.py +39 -0
  160. karrio/server/providers/models/utils.py +58 -0
  161. karrio/server/providers/router.py +3 -0
  162. karrio/server/providers/serializers/__init__.py +3 -0
  163. karrio/server/providers/serializers/base.py +538 -0
  164. karrio/server/providers/signals.py +25 -0
  165. karrio/server/providers/templates/providers/oauth_callback.html +105 -0
  166. karrio/server/providers/tests/__init__.py +5 -0
  167. karrio/server/providers/tests/test_connections.py +895 -0
  168. karrio/server/providers/urls.py +11 -0
  169. karrio/server/providers/views/__init__.py +0 -0
  170. karrio/server/providers/views/carriers.py +267 -0
  171. karrio/server/providers/views/connections.py +496 -0
  172. karrio/server/samples.py +352 -0
  173. karrio/server/serializers/__init__.py +2 -0
  174. karrio/server/serializers/abstract.py +602 -0
  175. karrio/server/tracing/__init__.py +0 -0
  176. karrio/server/tracing/admin.py +63 -0
  177. karrio/server/tracing/apps.py +8 -0
  178. karrio/server/tracing/migrations/0001_initial.py +41 -0
  179. karrio/server/tracing/migrations/0002_auto_20220710_1307.py +22 -0
  180. karrio/server/tracing/migrations/0003_auto_20221105_0317.py +43 -0
  181. karrio/server/tracing/migrations/0004_tracingrecord_carrier_account_idx.py +24 -0
  182. karrio/server/tracing/migrations/0005_optimise_tracingrecord_request_log_idx.py +25 -0
  183. karrio/server/tracing/migrations/0006_alter_tracingrecord_options_and_more.py +49 -0
  184. karrio/server/tracing/migrations/0007_tracingrecord_tracing_created_at_idx.py +19 -0
  185. karrio/server/tracing/migrations/__init__.py +0 -0
  186. karrio/server/tracing/models.py +82 -0
  187. karrio/server/tracing/tests.py +3 -0
  188. karrio/server/tracing/utils.py +109 -0
  189. karrio/server/user/__init__.py +0 -0
  190. karrio/server/user/admin.py +96 -0
  191. karrio/server/user/apps.py +7 -0
  192. karrio/server/user/forms.py +35 -0
  193. karrio/server/user/migrations/0001_initial.py +41 -0
  194. karrio/server/user/migrations/0002_token.py +29 -0
  195. karrio/server/user/migrations/0003_token_test_mode.py +20 -0
  196. karrio/server/user/migrations/0004_group.py +26 -0
  197. karrio/server/user/migrations/0005_token_label.py +21 -0
  198. karrio/server/user/migrations/0006_workspaceconfig.py +63 -0
  199. karrio/server/user/migrations/0007_user_metadata.py +25 -0
  200. karrio/server/user/migrations/__init__.py +0 -0
  201. karrio/server/user/models.py +218 -0
  202. karrio/server/user/serializers.py +47 -0
  203. karrio/server/user/templates/registration/login.html +108 -0
  204. karrio/server/user/templates/registration/registration_confirm_email.html +10 -0
  205. karrio/server/user/templates/registration/registration_confirm_email.txt +3 -0
  206. karrio/server/user/tests.py +3 -0
  207. karrio/server/user/urls.py +10 -0
  208. karrio/server/user/utils.py +60 -0
  209. karrio/server/user/views.py +9 -0
  210. karrio_server_core-2025.5.dist-info/METADATA +32 -0
  211. karrio_server_core-2025.5.dist-info/RECORD +213 -0
  212. karrio_server_core-2025.5.dist-info/WHEEL +5 -0
  213. karrio_server_core-2025.5.dist-info/top_level.txt +2 -0
@@ -0,0 +1,26 @@
1
+ # Generated by Django 3.2.14 on 2022-09-01 18:44
2
+
3
+ import django.contrib.auth.models
4
+ from django.db import migrations, models
5
+ import django.db.models.deletion
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('auth', '0012_alter_user_first_name_max_length'),
12
+ ('user', '0003_token_test_mode'),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name='Group',
18
+ fields=[
19
+ ('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.group')),
20
+ ],
21
+ bases=('auth.group',),
22
+ managers=[
23
+ ('objects', django.contrib.auth.models.GroupManager()),
24
+ ],
25
+ ),
26
+ ]
@@ -0,0 +1,21 @@
1
+ # Generated by Django 5.0 on 2023-12-08 05:41
2
+
3
+ import django.utils.timezone
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+ dependencies = [
9
+ ("user", "0004_group"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="token",
15
+ name="label",
16
+ field=models.CharField(
17
+ default=django.utils.timezone.now, max_length=50, verbose_name="label"
18
+ ),
19
+ preserve_default=False,
20
+ ),
21
+ ]
@@ -0,0 +1,63 @@
1
+ # Generated by Django 4.2.10 on 2024-03-12 18:03
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+ import django.db.models.deletion
6
+ import functools
7
+ import karrio.server.core.models
8
+ import karrio.server.core.models.base
9
+
10
+
11
+ class Migration(migrations.Migration):
12
+
13
+ dependencies = [
14
+ ("user", "0005_token_label"),
15
+ ]
16
+
17
+ operations = [
18
+ migrations.CreateModel(
19
+ name="WorkspaceConfig",
20
+ fields=[
21
+ ("created_at", models.DateTimeField(auto_now_add=True)),
22
+ ("updated_at", models.DateTimeField(auto_now=True)),
23
+ (
24
+ "id",
25
+ models.CharField(
26
+ default=functools.partial(
27
+ karrio.server.core.models.base.uuid,
28
+ *(),
29
+ **{"prefix": "wcfg_"}
30
+ ),
31
+ editable=False,
32
+ max_length=50,
33
+ primary_key=True,
34
+ serialize=False,
35
+ ),
36
+ ),
37
+ (
38
+ "config",
39
+ models.JSONField(
40
+ default=functools.partial(
41
+ karrio.server.core.models._identity, *(), **{"value": {}}
42
+ )
43
+ ),
44
+ ),
45
+ (
46
+ "created_by",
47
+ models.ForeignKey(
48
+ blank=True,
49
+ editable=False,
50
+ null=True,
51
+ on_delete=django.db.models.deletion.CASCADE,
52
+ to=settings.AUTH_USER_MODEL,
53
+ ),
54
+ ),
55
+ ],
56
+ options={
57
+ "verbose_name": "Workspace Config",
58
+ "verbose_name_plural": "Workspace Configs",
59
+ "db_table": "workspace-config",
60
+ },
61
+ bases=(karrio.server.core.models.base.ControlledAccessModel, models.Model),
62
+ ),
63
+ ]
@@ -0,0 +1,25 @@
1
+ # Generated by Django 5.2.7 on 2025-10-14 01:00
2
+
3
+ import functools
4
+ import karrio.server.core.models
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ("user", "0006_workspaceconfig"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="user",
17
+ name="metadata",
18
+ field=models.JSONField(
19
+ default=functools.partial(
20
+ karrio.server.core.models._identity, *(), **{"value": {}}
21
+ ),
22
+ help_text="User defined metadata",
23
+ ),
24
+ ),
25
+ ]
File without changes
@@ -0,0 +1,218 @@
1
+ import os
2
+ import binascii
3
+ import functools
4
+ from django.db import models
5
+ from django.conf import settings
6
+ from django.contrib.auth import models as auth
7
+ from rest_framework.authtoken import models as authtoken
8
+ from django.contrib.contenttypes.models import ContentType
9
+ from django.utils.translation import gettext_lazy as _
10
+
11
+ import karrio.server.core.models as core
12
+
13
+
14
+ class UserManager(auth.UserManager):
15
+ def _create_user(self, email, password, **extra_fields):
16
+ """
17
+ Create and save a user with the given username, email, and password.
18
+ """
19
+ if not email:
20
+ raise ValueError("The given email must be set")
21
+ email = self.normalize_email(email)
22
+ user = self.model(email=email, **extra_fields)
23
+ user.set_password(password)
24
+ user.save(using=self._db)
25
+ return user
26
+
27
+ def create_user(self, email, password=None, **extra_fields):
28
+ extra_fields.setdefault("is_staff", False)
29
+ extra_fields.setdefault("is_superuser", False)
30
+ return self._create_user(email, password, **extra_fields)
31
+
32
+ def create_superuser(self, email, password=None, **extra_fields):
33
+ extra_fields.setdefault("is_staff", True)
34
+ extra_fields.setdefault("is_superuser", True)
35
+
36
+ if extra_fields.get("is_staff") is not True:
37
+ raise ValueError("Superuser must have is_staff=True.")
38
+ if extra_fields.get("is_superuser") is not True:
39
+ raise ValueError("Superuser must have is_superuser=True.")
40
+
41
+ return self._create_user(email, password, **extra_fields)
42
+
43
+
44
+ @core.register_model
45
+ class User(auth.AbstractUser):
46
+ full_name = models.CharField(_("full name"), max_length=150, blank=True)
47
+ email = models.EmailField(_("email address"), unique=True)
48
+ metadata = models.JSONField(
49
+ default=core.field_default({}), help_text="User defined metadata"
50
+ )
51
+
52
+ USERNAME_FIELD = "email"
53
+ REQUIRED_FIELDS: list = []
54
+
55
+ objects = UserManager()
56
+
57
+ username = None
58
+ first_name = None
59
+ last_name = None
60
+
61
+ def __str__(self):
62
+ return self.email
63
+
64
+ def delete(self, *args, **kwargs):
65
+ self.tokens.all().delete()
66
+ super().delete(*args, **kwargs)
67
+
68
+ @property
69
+ def object_type(self):
70
+ return "user"
71
+
72
+ @property
73
+ def permissions(self):
74
+ import karrio.server.conf as conf
75
+ import karrio.server.iam.models as iam
76
+ import karrio.server.core.middleware as middleware
77
+ from django.utils.functional import SimpleLazyObject
78
+
79
+ ctx = middleware.SessionContext.get_current_request()
80
+
81
+ # Helper to evaluate org safely
82
+ def get_org():
83
+ return (
84
+ None if not all([
85
+ conf.settings.MULTI_ORGANIZATIONS,
86
+ ctx is not None,
87
+ hasattr(ctx, "org"),
88
+ ctx.org is not None,
89
+ ]) else (
90
+ ctx.org if not isinstance(ctx.org, SimpleLazyObject)
91
+ else (ctx.org if ctx.org else None)
92
+ )
93
+ )
94
+
95
+ # Helper to get context permissions
96
+ def get_context_perms():
97
+ org = get_org()
98
+ org_user = org.organization_users.filter(user_id=self.pk).first() if org else None
99
+
100
+ try:
101
+ context_permission = iam.ContextPermission.objects.get(
102
+ object_pk=org_user.pk,
103
+ content_type=ContentType.objects.get_for_model(org_user),
104
+ ) if org_user else None
105
+
106
+ return list(context_permission.groups.all().values_list("name", flat=True)) if context_permission else []
107
+ except iam.ContextPermission.DoesNotExist:
108
+ return []
109
+
110
+ # Functional chain of permission resolution
111
+ _permissions = get_context_perms() or list(self.groups.all().values_list("name", flat=True))
112
+
113
+ return (
114
+ list(Group.objects.all().values_list("name", flat=True)) if self.is_superuser and not _permissions
115
+ else list(Group.objects.exclude(name__in=["manage_system", "manage_team", "manage_org_owner"]).values_list("name", flat=True)) if self.is_staff and not _permissions
116
+ else _permissions
117
+ )
118
+
119
+
120
+ @core.register_model
121
+ class Token(authtoken.Token, core.ControlledAccessModel):
122
+ label = models.CharField(_("label"), max_length=50)
123
+ user = models.ForeignKey(
124
+ settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="tokens"
125
+ )
126
+ test_mode = models.BooleanField(null=False, default=core.field_default(False))
127
+
128
+ class Meta:
129
+ verbose_name = _("Token")
130
+ verbose_name_plural = _("Tokens")
131
+
132
+ @property
133
+ def pk(self):
134
+ return self.key
135
+
136
+ @classmethod
137
+ def generate_key(cls):
138
+ return f"key_{binascii.hexlify(os.urandom(16)).decode()}"
139
+
140
+ @property
141
+ def organization(self):
142
+ return self.org.first() if hasattr(self, "org") else None
143
+
144
+ @property
145
+ def object_type(self):
146
+ return "token"
147
+
148
+ @property
149
+ def permissions(self):
150
+ import karrio.server.conf as conf
151
+ import karrio.server.iam.models as iam
152
+
153
+ _permissions = []
154
+
155
+ if iam.ContextPermission.objects.filter(object_pk=self.pk).exists():
156
+ _permissions = (
157
+ iam.ContextPermission.objects.get(
158
+ object_pk=self.pk,
159
+ content_type=ContentType.objects.get_for_model(Token),
160
+ )
161
+ .groups.all()
162
+ .values_list("name", flat=True)
163
+ )
164
+
165
+ if (
166
+ not any(_permissions)
167
+ and conf.settings.MULTI_ORGANIZATIONS
168
+ and self.org.exists()
169
+ ):
170
+ org_user = self.org.first().organization_users.filter(user_id=self.user_id)
171
+ _permissions = (
172
+ iam.ContextPermission.objects.get(
173
+ object_pk=org_user.first().pk,
174
+ content_type=ContentType.objects.get_for_model(org_user.first()),
175
+ )
176
+ .groups.all()
177
+ .values_list("name", flat=True)
178
+ if org_user.exists()
179
+ else []
180
+ )
181
+
182
+ return _permissions if any(_permissions) else self.user.permissions
183
+
184
+
185
+ @core.register_model
186
+ class Group(auth.Group):
187
+ pass
188
+
189
+
190
+ @core.register_model
191
+ class WorkspaceConfig(core.OwnedEntity):
192
+ class Meta:
193
+ db_table = "workspace-config"
194
+ verbose_name = "Workspace Config"
195
+ verbose_name_plural = "Workspace Configs"
196
+
197
+ id = models.CharField(
198
+ max_length=50,
199
+ editable=False,
200
+ primary_key=True,
201
+ default=functools.partial(core.uuid, prefix="wcfg_"),
202
+ )
203
+ config = models.JSONField(
204
+ null=False,
205
+ blank=False,
206
+ default=core.field_default({}),
207
+ )
208
+ created_by = models.ForeignKey(
209
+ User,
210
+ blank=True,
211
+ null=True,
212
+ on_delete=models.CASCADE,
213
+ editable=False,
214
+ )
215
+
216
+ @property
217
+ def object_type(self):
218
+ return "workspace-config"
@@ -0,0 +1,47 @@
1
+ import karrio.server.conf as conf
2
+ import karrio.server.user.models as models
3
+ import karrio.server.serializers as serializers
4
+
5
+
6
+ @serializers.owned_model_serializer
7
+ class TokenSerializer(serializers.Serializer):
8
+ label = serializers.CharField(required=False)
9
+
10
+ def create(
11
+ self, validated_data: dict, context: serializers.Context
12
+ ) -> models.Token:
13
+ return models.Token.objects.create(
14
+ user=context.user,
15
+ test_mode=context.test_mode,
16
+ label=validated_data.get("label") or "Default API Key",
17
+ )
18
+
19
+ @staticmethod
20
+ def retrieve_token(context, org_id: str = None):
21
+ org = getattr(context, "org", None)
22
+ org_id = org_id or getattr(org, "id", None)
23
+
24
+ queyset = models.Token.objects.filter(
25
+ **{
26
+ "test_mode": getattr(context, "test_mode", None),
27
+ "user__id": getattr(getattr(context, "user", None), "id", None),
28
+ **({"org__id": org_id} if org_id is not None else {}),
29
+ }
30
+ )
31
+
32
+ if queyset.exists():
33
+ return queyset.first()
34
+
35
+ # Handle multi-organization mode
36
+ if org_id is not None and conf.settings.MULTI_ORGANIZATIONS:
37
+ import karrio.server.orgs.models as orgs
38
+
39
+ org = orgs.Organization.objects.get(id=org_id, users__id=context.user.id)
40
+
41
+ ctx = serializers.Context(
42
+ org=org,
43
+ user=getattr(context, "user", None),
44
+ test_mode=getattr(context, "test_mode", None),
45
+ )
46
+
47
+ return TokenSerializer.map(data={}, context=ctx).save().instance
@@ -0,0 +1,108 @@
1
+ {% extends "admin/base_site.html" %}
2
+ {% load i18n static %}
3
+
4
+ {% block extrastyle %}
5
+ {{ block.super }}
6
+ <link rel="stylesheet" type="text/css" href="{% static "css/login.css" %}">
7
+ {{ form.media }}
8
+ {% endblock %}
9
+
10
+
11
+ {% block bodyclass %}{{ block.super }} login{% endblock %}
12
+
13
+ {% block usertools %}{% endblock %}
14
+
15
+ {% block nav-global %}{% endblock %}
16
+
17
+ {% block nav-sidebar %}{% endblock %}
18
+
19
+ {% block content_title %}{% endblock %}
20
+
21
+ {% block breadcrumbs %}{% endblock %}
22
+
23
+ {% block content %}
24
+ <div id="content-main">
25
+
26
+ {% if wizard.steps.current == 'token' %}
27
+ {% if device.method == 'call' %}
28
+ <p>{% blocktrans trimmed %}We are calling your phone right now, please enter the
29
+ digits you hear.{% endblocktrans %}</p>
30
+ {% elif device.method == 'sms' %}
31
+ <p>{% blocktrans trimmed %}We sent you a text message, please enter the tokens we
32
+ sent.{% endblocktrans %}</p>
33
+ {% else %}
34
+ <p>{% blocktrans trimmed %}Please enter the tokens generated by your token
35
+ generator.{% endblocktrans %}</p>
36
+ {% endif %}
37
+ {% elif wizard.steps.current == 'backup' %}
38
+ <p>{% blocktrans trimmed %}Use this form for entering backup tokens for logging in.
39
+ These tokens have been generated for you to print and keep safe. Please
40
+ enter one of these backup tokens to login to your account.{% endblocktrans %}</p>
41
+ {% endif %}
42
+
43
+ {% if form.errors and not form.non_field_errors %}
44
+ <p class="errornote">
45
+ {% if form.errors.items|length == 1 %}
46
+ {% translate "Please correct the error below." %}
47
+ {% else %}
48
+ {% translate "Please correct the errors below." %}
49
+ {% endif %}
50
+ </p>
51
+ {% endif %}
52
+ {% if form.non_field_errors %}
53
+ {% for error in form.non_field_errors %}
54
+ <p class="errornote">{{ error }}</p>
55
+ {% endfor %}
56
+ {% endif %}
57
+
58
+ <form action="" method="post" id="login-form">{% csrf_token %}
59
+ {{ wizard.management_form }}
60
+
61
+ {% if wizard.steps.current == 'auth' %}
62
+ <div class="form-row">
63
+ {{ form.username.errors }}
64
+ {{ form.username.label_tag }} {{ form.username }}
65
+ </div>
66
+ <div class="form-row">
67
+ {{ form.password.errors }}
68
+ {{ form.password.label_tag }} {{ form.password }}
69
+ </div>
70
+ <div class="submit-row">
71
+ <input type="submit" value="{% translate 'Log in' %}">
72
+ </div>
73
+ {% else %}
74
+ <div class="form-row">
75
+ {{ form.otp_token.errors }}
76
+ {{ form.otp_token.label_tag }} {{ form.otp_token }}
77
+ </div>
78
+
79
+ <div class="submit-row">
80
+ <input type="submit" value="{% translate 'Continue' %}">
81
+ </div>
82
+ {% endif %}
83
+
84
+ {% if other_devices %}
85
+ <p>{% trans "Or, alternatively, use one of your backup phones:" %}</p>
86
+ <p>
87
+ {% for other in other_devices %}
88
+ <button name="challenge_device" value="{{ other.persistent_id }}"
89
+ class="btn btn-secondary btn-block" type="submit">
90
+ {{ other.generate_challenge_button_title }}
91
+ </button>
92
+ {% endfor %}</p>
93
+ {% endif %}
94
+ {% if backup_tokens %}
95
+ <p>{% trans "As a last resort, you can use a backup token:" %}</p>
96
+ <p>
97
+ <button name="wizard_goto_step" type="submit" value="backup"
98
+ class="btn btn-secondary btn-block">{% trans "Use Backup Token" %}</button>
99
+ </p>
100
+ {% endif %}
101
+
102
+ <input type="hidden" name="next" value="{{ request.GET.next }}"/>
103
+ {# hidden submit button to enable [enter] key #}
104
+ <input type="submit" value="" style="display: none;" />
105
+ </form>
106
+ </div>
107
+ {% endblock %}
108
+
@@ -0,0 +1,10 @@
1
+ {% load i18n %}{% autoescape off %}
2
+ {% block title %}Email confirmation{% endblock %}
3
+
4
+ <p>{% trans "You are almost there," %}</p>
5
+
6
+ <p>{% trans "Please click the following link to verify your email address." %}</p>
7
+
8
+ <p><a href="{{ link }}">{{ link }}</a></p>
9
+
10
+ {% endautoescape %}
@@ -0,0 +1,3 @@
1
+ You are almost there,
2
+ Please click the following link to verify your email address:
3
+ {{ link }}
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.
@@ -0,0 +1,10 @@
1
+ """
2
+ karrio server user module urls
3
+ """
4
+ from django.urls import include, path
5
+ from django_email_verification import urls as mail_urls
6
+
7
+ urlpatterns = [
8
+ path("email/", include(mail_urls)),
9
+ path("", include("karrio.server.user.views")),
10
+ ]
@@ -0,0 +1,60 @@
1
+ import datetime
2
+ from django_email_verification.token_utils import default_token_generator
3
+ from django_email_verification.confirm import (
4
+ Thread,
5
+ InvalidUserModel,
6
+ EmailMultiAlternatives,
7
+ _get_validated_field,
8
+ render_to_string,
9
+ )
10
+
11
+
12
+ def send_email(user, redirect_url, thread=True, expiry=None, **kwargs):
13
+ try:
14
+ user.save()
15
+
16
+ if kwargs.get("custom_salt"):
17
+ default_token_generator.key_salt = kwargs["custom_salt"]
18
+
19
+ _expiry = expiry or (datetime.datetime.now() + datetime.timedelta(days=30))
20
+ token, __expiry = default_token_generator.make_token(user, _expiry, kind="MAIL")
21
+
22
+ domain = redirect_url
23
+ sender = _get_validated_field("EMAIL_FROM_ADDRESS")
24
+ subject = _get_validated_field("EMAIL_MAIL_SUBJECT")
25
+ mail_plain = _get_validated_field("EMAIL_MAIL_PLAIN")
26
+ mail_html = _get_validated_field("EMAIL_MAIL_HTML")
27
+
28
+ args = (
29
+ user,
30
+ token,
31
+ __expiry,
32
+ sender,
33
+ domain,
34
+ subject,
35
+ mail_plain,
36
+ mail_html,
37
+ )
38
+ if thread:
39
+ t = Thread(target=send_email_thread, args=args)
40
+ t.start()
41
+ else:
42
+ send_email_thread(*args)
43
+ except AttributeError:
44
+ raise InvalidUserModel("The user model you provided is invalid")
45
+
46
+
47
+ def send_email_thread(
48
+ user, token, expiry, sender, domain, subject, mail_plain, mail_html
49
+ ):
50
+ domain += "/" if not domain.endswith("/") else ""
51
+ link = domain + token
52
+
53
+ context = {"link": link, "expiry": expiry, "user": user}
54
+
55
+ text = render_to_string(mail_plain, context)
56
+ html = render_to_string(mail_html, context)
57
+
58
+ msg = EmailMultiAlternatives(subject, text, sender, [user.email])
59
+ msg.attach_alternative(html, "text/html")
60
+ msg.send()
@@ -0,0 +1,9 @@
1
+ from django.urls import path
2
+ from two_factor.views import LoginView as BaseLoginView
3
+
4
+
5
+ class LoginView(BaseLoginView):
6
+ template_name = "registration/login.html"
7
+
8
+
9
+ urlpatterns = [path("login/", LoginView.as_view(), name="login")]
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: karrio_server_core
3
+ Version: 2025.5
4
+ Summary: Multi-carrier shipping API Core module
5
+ Author-email: karrio <hello@karrio.io>
6
+ License-Expression: LGPL-3.0
7
+ Project-URL: Homepage, https://github.com/karrioapi/karrio
8
+ Classifier: Programming Language :: Python :: 3
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: karrio
12
+ Requires-Dist: psycopg2-binary
13
+ Requires-Dist: django-health-check
14
+ Requires-Dist: psutil
15
+ Requires-Dist: pyyaml
16
+ Requires-Dist: Jinja2
17
+
18
+ # karrio.server.core
19
+
20
+ This package is a module of the [karrio](https://pypi.org/project/karrio.server) universal shipping API.
21
+
22
+ ## Requirements
23
+
24
+ `Python 3.11+`
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install karrio.server.core
30
+ ```
31
+
32
+ Check the [karrio docs](https://docs.karrio.io) to get started.