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,310 @@
1
+ import jinja2
2
+ import django.conf as django
3
+ import drf_spectacular.views as views
4
+ import rest_framework.response as response
5
+
6
+ import django.urls as urls
7
+ import karrio.server.conf as conf
8
+ import karrio.server.core.dataunits as dataunits
9
+
10
+ VERSION = getattr(django.settings, "VERSION", "")
11
+ non_null = lambda items: [i for i in items if i is not None]
12
+ RedocView = views.SpectacularRedocView.as_view(
13
+ url_name="shipping-openapi",
14
+ template_name="openapi/openapi.html",
15
+ )
16
+
17
+
18
+ class ShippingOpenAPIView(views.SpectacularAPIView):
19
+ def _get_schema_response(self, request):
20
+ version = (
21
+ self.api_version or request.version or self._get_version_parameter(request)
22
+ )
23
+ generator = self.generator_class(
24
+ urlconf=self.urlconf, api_version=version, patterns=self.patterns
25
+ )
26
+ data = generator.get_schema(request=request, public=self.serve_public)
27
+
28
+ data["tags"] = render_tags(request, conf.settings.APP_NAME)
29
+ data["components"]["securitySchemes"] = {
30
+ k: v
31
+ for k, v in data["components"]["securitySchemes"].items()
32
+ if k in ["JWT", "OAuth2", "Token", "TokenBasic"]
33
+ }
34
+ data["info"] = dict(
35
+ description=render_schema_description(conf.settings.APP_NAME),
36
+ title=f"{conf.settings.APP_NAME} API",
37
+ version=conf.settings.VERSION,
38
+ )
39
+
40
+ return response.Response(
41
+ data=data,
42
+ headers={
43
+ "Content-Disposition": f'inline; filename="{self._get_filename(request, version)}"'
44
+ },
45
+ )
46
+
47
+
48
+ urlpatterns = [
49
+ urls.path(
50
+ django.settings.OPEN_API_PATH,
51
+ RedocView,
52
+ name="schema-rapi",
53
+ ),
54
+ urls.path(
55
+ "shipping-openapi",
56
+ ShippingOpenAPIView.as_view(),
57
+ name="shipping-openapi",
58
+ ),
59
+ ]
60
+
61
+
62
+ def render_schema_description(APP_NAME):
63
+ return f"""
64
+ {APP_NAME} is a multi-carrier shipping API that simplifies the integration of logistics carrier services.
65
+
66
+ The {APP_NAME} API is organized around REST. Our API has predictable resource-oriented URLs, accepts JSON-encoded
67
+ request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.
68
+
69
+ The {APP_NAME} API differs for every account as we release new versions.
70
+ These docs are customized to your version of the API.
71
+
72
+
73
+ ## Versioning
74
+
75
+ When backwards-incompatible changes are made to the API, a new, dated version is released.
76
+ The current version is `{VERSION}`.
77
+
78
+ Read our API changelog to learn more about backwards compatibility.
79
+
80
+ As a precaution, use API versioning to check a new API version before committing to an upgrade.
81
+
82
+
83
+ ## Environments
84
+
85
+ The {APP_NAME} API offer the possibility to create and retrieve certain objects in `test_mode`.
86
+ In development, it is therefore possible to add carrier connections, get live rates,
87
+ buy labels, create trackers and schedule pickups in `test_mode`.
88
+
89
+
90
+ ## Pagination
91
+
92
+ All top-level API resources have support for bulk fetches via "list" API methods. For instance, you can list addresses,
93
+ list shipments, and list trackers. These list API methods share a common structure, taking at least these
94
+ two parameters: limit, and offset.
95
+
96
+ {APP_NAME} utilizes offset-based pagination via the offset and limit parameters.
97
+ Both parameters take a number as value (see below) and return objects in reverse chronological order.
98
+ The offset parameter returns objects listed after an index.
99
+ The limit parameter take a limit on the number of objects to be returned from 1 to 100.
100
+
101
+
102
+ ```json
103
+ {{
104
+ "count": 100,
105
+ "next": "/v1/shipments?limit=25&offset=50",
106
+ "previous": "/v1/shipments?limit=25&offset=25",
107
+ "results": [
108
+ {{ ... }},
109
+ ]
110
+ }}
111
+ ```
112
+
113
+ ## Metadata
114
+
115
+ Updateable {APP_NAME} objects—including Shipment and Order have a metadata parameter.
116
+ You can use this parameter to attach key-value data to these {APP_NAME} objects.
117
+
118
+ Metadata is useful for storing additional, structured information on an object.
119
+ As an example, you could store your user's full name and corresponding unique identifier
120
+ from your system on a {APP_NAME} Order object.
121
+
122
+ Do not store any sensitive information as metadata.
123
+
124
+ ## Authentication
125
+
126
+ API keys are used to authenticate requests. You can view and manage your API keys in the Dashboard.
127
+
128
+ Your API keys carry many privileges, so be sure to keep them secure! Do not share your secret
129
+ API keys in publicly accessible areas such as GitHub, client-side code, and so forth.
130
+
131
+ Authentication to the API is performed via HTTP Basic Auth. Provide your API token as
132
+ the basic auth username value. You do not need to provide a password.
133
+
134
+ ```shell
135
+ $ curl https://instance.api.com/v1/shipments \\
136
+ -u key_xxxxxx:
137
+ # The colon prevents curl from asking for a password.
138
+ ```
139
+
140
+ If you need to authenticate via bearer auth (e.g., for a cross-origin request),
141
+ use `-H "Authorization: Token key_xxxxxx"` instead of `-u key_xxxxxx`.
142
+
143
+ All API requests must be made over [HTTPS](http://en.wikipedia.org/wiki/HTTP_Secure).
144
+ API requests without authentication will also fail.
145
+ """
146
+
147
+
148
+ def render_reference_descriptions(request):
149
+ refs = dataunits.contextual_reference(request, reduced=False)
150
+
151
+ def format_preset(preset: dict):
152
+ vals = [
153
+ str(v)
154
+ for v in [
155
+ preset.get("width"),
156
+ preset.get("height"),
157
+ preset.get("length"),
158
+ ]
159
+ if v is not None
160
+ ]
161
+
162
+ return f'{" x ".join(vals)} {preset.get("dimension_unit").lower()}'
163
+
164
+ template = """## Carriers
165
+ | Carrier Name | Display Name |
166
+ | ------------ | ------------ |
167
+ {% for carrier, name in refs.get("carriers", {}).items() -%}{% if carrier != "generic" -%}
168
+ | {{ carrier }} | {{ name }} |
169
+ {% endif -%}{% endfor %}
170
+ ---
171
+ ## Services
172
+ The following service level codes can be used to reference specific rates
173
+ when purchasing shipping labels using single call label creation.
174
+ You can also find all of the possible service levels for each of your carrier
175
+ accounts by using [this endpoint](#operation/&&get_services).
176
+ {% for carrier, services in refs.get("services", {}).items() -%}{% if carrier != "generic" -%}
177
+ ### {{ refs.get("carriers", {}).get(carrier, "") }}
178
+ | Code | Service Name |
179
+ | ------------ | ------------ |
180
+ {% for code, name in services.items() -%}
181
+ | {{ code }} | {{ name }} |
182
+ {% endfor %}
183
+ {% endif -%}{% endfor %}
184
+ ---
185
+ ## Parcel Templates
186
+ Use any of the following templates when you ship with special carrier packaging.
187
+ {% for carrier, presets in refs.get("package_presets", {}).items() -%}
188
+ ### {{ refs.get("carriers", {}).get(carrier, "") }}
189
+ | Code | Dimensions |
190
+ | ------------ | ------------ |
191
+ {% for code, preset in presets.items() -%}
192
+ | {{ code }} | {{ format_preset(preset) }} |
193
+ {% endfor %}
194
+ {% endfor %}
195
+ """
196
+
197
+ return jinja2.Template(template).render(refs=refs, format_preset=format_preset)
198
+
199
+
200
+ def render_tags(request, APP_NAME):
201
+ return non_null(
202
+ [
203
+ {
204
+ "name": "API",
205
+ "description": """API instance metadata resources.
206
+ """,
207
+ },
208
+ {
209
+ "name": "Auth",
210
+ "description": """API authentication resources.
211
+ """,
212
+ },
213
+ {
214
+ "name": "Carriers",
215
+ "description": f"""This is an object representing your {APP_NAME} carrier extension.
216
+ You can retrieve all supported carrier extensions available.
217
+ """,
218
+ },
219
+ {
220
+ "name": "Connections",
221
+ "description": f"""This is an object representing your {APP_NAME} carrier connections.
222
+ You can retrieve all carrier connections available to your account.
223
+ The `carrier_id` is a friendly name you assign to your connection.
224
+ """,
225
+ },
226
+ {
227
+ "name": "Addresses",
228
+ "description": f"""This is an object representing your {APP_NAME} shipping address.
229
+ You can retrieve all addresses related to your {APP_NAME} account.
230
+ Address objects are linked to your shipment history, and can be used for recurring shipping
231
+ to / from the same locations.
232
+ """,
233
+ },
234
+ {
235
+ "name": "Parcels",
236
+ "description": f"""This is an object representing your {APP_NAME} shipping parcel.
237
+ Parcel objects are linked to your shipment history, and can be used for recurring shipping
238
+ using the same packaging.
239
+ """,
240
+ },
241
+ {
242
+ "name": "Shipments",
243
+ "description": f"""This is an object representing your {APP_NAME} shipment.
244
+ A Shipment guides you through process of preparing and purchasing a label for an order.
245
+ A Shipment transitions through multiple statuses throughout its lifetime as the package
246
+ shipped makes its journey to it's destination.
247
+ """,
248
+ },
249
+ {
250
+ "name": "Documents",
251
+ "description": f"""This is an object representing your {APP_NAME} document upload record.
252
+ A Document upload record keep traces of shipping trade documents uploaded to carriers
253
+ to fast track customs and border processing.
254
+ """,
255
+ },
256
+ {
257
+ "name": "Manifests",
258
+ "description": f"""This is an object representing your {APP_NAME} manifest details.
259
+ Some carriers require manifests to be created after labels are generated.
260
+ A manifest is a summary of all the shipments that are being sent out.
261
+ """,
262
+ },
263
+ {
264
+ "name": "Trackers",
265
+ "description": f"""This is an object representing your {APP_NAME} shipment tracker.
266
+ A shipment tracker is an object attached to a shipment by it's tracking number.
267
+ The tracker provide the latest tracking status and events associated with a shipment
268
+ """,
269
+ },
270
+ {
271
+ "name": "Pickups",
272
+ "description": f"""This is an object representing your {APP_NAME} pickup booking.
273
+ You can retrieve all pickup booked historically for your {APP_NAME} account shipments.
274
+ """,
275
+ },
276
+ {
277
+ "name": "Proxy",
278
+ "description": f"""In some scenarios, all we need is to send request to a carrier using the {APP_NAME} unified API.
279
+ The Proxy API comes handy for that as it turn {APP_NAME} into a simple middleware that converts and
280
+ validate your request and simply forward it to the shipping carrier server.<br/>
281
+ **Note:**<br/>
282
+ When using the proxy API, no objects are created in the {APP_NAME} system.
283
+ excpet API logs and tracing records for debugging purposes.
284
+ """,
285
+ },
286
+ {
287
+ "name": "Orders",
288
+ "description": f"""This is an object representing your {APP_NAME} order.
289
+ You can create {APP_NAME} orders to organize your shipments and ship line items separately.
290
+ """,
291
+ },
292
+ {
293
+ "name": "Webhooks",
294
+ "description": f"""This is an object representing your {APP_NAME} webhook.
295
+ You can configure webhook endpoints via the API to be notified about events happen in your
296
+ {APP_NAME} account.
297
+ """,
298
+ },
299
+ {
300
+ "name": "Batches",
301
+ "description": f"""This is an object representing your {APP_NAME} batch operation.
302
+ You can retrieve all batch operations historically for your {APP_NAME} account.
303
+ """,
304
+ },
305
+ {
306
+ "name": "Reference & Enums",
307
+ "description": render_reference_descriptions(request),
308
+ },
309
+ ]
310
+ )
@@ -0,0 +1,2 @@
1
+ from django_filters import *
2
+ from karrio.server.filters.abstract import *
@@ -0,0 +1,26 @@
1
+ import django_filters
2
+ import karrio.lib as lib
3
+
4
+
5
+ class CharInFilter(django_filters.BaseInFilter, django_filters.CharFilter):
6
+ pass
7
+
8
+
9
+ class FilterSet(django_filters.FilterSet):
10
+ def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
11
+ data = {
12
+ **data,
13
+ **{
14
+ key: (
15
+ ",".join(val)
16
+ if self.base_filters.get(key).__class__ == CharInFilter
17
+ else val
18
+ )
19
+ for key, val in data.items()
20
+ },
21
+ }
22
+ super().__init__(data, queryset, request=request, prefix=prefix)
23
+
24
+ def to_dict(self):
25
+ self.form.is_valid()
26
+ return lib.to_dict(self.form.cleaned_data)
File without changes
@@ -0,0 +1,3 @@
1
+ from django.contrib import admin
2
+
3
+ # Register your models here.
@@ -0,0 +1,21 @@
1
+ from django.apps import AppConfig
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+
5
+ class IamConfig(AppConfig):
6
+ name = "karrio.server.iam"
7
+ verbose_name = _("IAM")
8
+ default_auto_field = "django.db.models.BigAutoField"
9
+
10
+ def ready(self):
11
+ from karrio.server.core import utils
12
+ from karrio.server.iam import signals, permissions
13
+
14
+ @utils.skip_on_commands()
15
+ def _init():
16
+ signals.register_all()
17
+
18
+ # Setup default permission groups and apply to existing orgs on start up
19
+ utils.run_on_all_tenants(permissions.setup_groups)()
20
+
21
+ _init()
@@ -0,0 +1,33 @@
1
+ # Generated by Django 3.2.14 on 2022-09-03 12:25
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = [
12
+ ('user', '0004_group'),
13
+ ('auth', '0012_alter_user_first_name_max_length'),
14
+ ('contenttypes', '0002_remove_content_type_name'),
15
+ ]
16
+
17
+ operations = [
18
+ migrations.CreateModel(
19
+ name='ContextPermission',
20
+ fields=[
21
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
23
+ ('object_pk', models.CharField(db_index=True, max_length=50, verbose_name='object pk')),
24
+ ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype', verbose_name='content type')),
25
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user context belongs to. A user will get all permissions granted to each of their groups.', related_name='context', related_query_name='context', to='user.Group', verbose_name='groups')),
26
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user context.', related_name='context', related_query_name='context', to='auth.Permission', verbose_name='user context permissions')),
27
+ ],
28
+ options={
29
+ 'verbose_name': 'context permission',
30
+ 'verbose_name_plural': 'context permission',
31
+ },
32
+ ),
33
+ ]
File without changes
@@ -0,0 +1,48 @@
1
+ from django.db import models
2
+ from django.contrib.auth.models import PermissionsMixin, Permission
3
+ from django.contrib.contenttypes.fields import GenericForeignKey
4
+ from django.utils.translation import gettext_lazy as _
5
+
6
+ import karrio.server.user.models as user
7
+
8
+ User = user.User
9
+ Group = user.Group
10
+
11
+
12
+ class ContextPermission(PermissionsMixin):
13
+ class Meta:
14
+ verbose_name = _("context permission")
15
+ verbose_name_plural = _("context permission")
16
+
17
+ def __str__(self) -> str:
18
+ return f"{self.object_pk} - {self.content_type}"
19
+
20
+ content_type = models.ForeignKey(
21
+ to="contenttypes.ContentType",
22
+ on_delete=models.CASCADE,
23
+ related_name="+",
24
+ verbose_name=_("content type"),
25
+ )
26
+ object_pk = models.CharField(
27
+ db_index=True, max_length=50, verbose_name=_("object pk")
28
+ )
29
+ content_object = GenericForeignKey("content_type", "object_pk")
30
+ groups = models.ManyToManyField(
31
+ Group,
32
+ verbose_name=_("groups"),
33
+ blank=True,
34
+ help_text=_(
35
+ "The groups this user context belongs to. A user will get all permissions "
36
+ "granted to each of their groups."
37
+ ),
38
+ related_name="context",
39
+ related_query_name="context",
40
+ )
41
+ user_permissions = models.ManyToManyField(
42
+ Permission,
43
+ verbose_name=_("user context permissions"),
44
+ blank=True,
45
+ help_text=_("Specific permissions for this user context."),
46
+ related_name="context",
47
+ related_query_name="context",
48
+ )
@@ -0,0 +1,155 @@
1
+ import typing
2
+ from django.db import models
3
+ from django.contrib.auth import get_user_model
4
+ from django.contrib.auth.models import Permission
5
+ from karrio.server.core.logging import logger
6
+
7
+ import karrio.server.core.utils as utils
8
+ import karrio.server.user.models as users
9
+ import karrio.server.iam.serializers as serializers
10
+
11
+ User = get_user_model()
12
+
13
+
14
+ @utils.skip_on_loadata
15
+ @utils.async_wrapper
16
+ @utils.tenant_aware
17
+ def setup_groups(**_):
18
+ """This function create all standard group permissions if they don't exsist."""
19
+ logger.info("Setting up permissions")
20
+
21
+ # manage_apps
22
+ setup_group(
23
+ serializers.PermissionGroup.manage_apps.name,
24
+ permissions=Permission.objects.filter(content_type__app_label="apps"),
25
+ )
26
+
27
+ # manage_carriers (deprecated - kept for backward compatibility)
28
+ setup_group(
29
+ serializers.PermissionGroup.manage_carriers.name,
30
+ permissions=[
31
+ *Permission.objects.filter(content_type__app_label="providers"),
32
+ *Permission.objects.filter(
33
+ models.Q(content_type__app_label="orgs")
34
+ & models.Q(name__icontains="carrier")
35
+ ),
36
+ ],
37
+ override=True,
38
+ )
39
+
40
+ # read_carriers (view permissions only)
41
+ setup_group(
42
+ serializers.PermissionGroup.read_carriers.name,
43
+ permissions=Permission.objects.filter(
44
+ content_type__app_label="providers", name__icontains="view"
45
+ ),
46
+ override=True,
47
+ )
48
+
49
+ # write_carriers (create, update, delete permissions)
50
+ setup_group(
51
+ serializers.PermissionGroup.write_carriers.name,
52
+ permissions=[
53
+ *Permission.objects.filter(content_type__app_label="providers"),
54
+ *Permission.objects.filter(
55
+ models.Q(content_type__app_label="orgs")
56
+ & models.Q(name__icontains="carrier")
57
+ ),
58
+ ],
59
+ override=True,
60
+ )
61
+
62
+ # manage_orders
63
+ setup_group(
64
+ serializers.PermissionGroup.manage_orders.name,
65
+ permissions=Permission.objects.filter(content_type__app_label="orders"),
66
+ )
67
+
68
+ # manage_team
69
+ setup_group(
70
+ serializers.PermissionGroup.manage_team.name,
71
+ permissions=(
72
+ Permission.objects.filter(
73
+ content_type__app_label="orgs", name__icontains="organization"
74
+ ).exclude(name__icontains="owner")
75
+ ),
76
+ override=True,
77
+ )
78
+
79
+ # manage_org_owner
80
+ setup_group(
81
+ serializers.PermissionGroup.manage_org_owner.name,
82
+ permissions=Permission.objects.filter(
83
+ content_type__model="OrganizationOwner".lower()
84
+ ),
85
+ )
86
+
87
+ # manage_webhooks
88
+ setup_group(
89
+ serializers.PermissionGroup.manage_webhooks.name,
90
+ permissions=Permission.objects.filter(content_type__model="Webhook".lower()),
91
+ )
92
+
93
+ # manage_data
94
+ setup_group(
95
+ serializers.PermissionGroup.manage_data.name,
96
+ permissions=[
97
+ *Permission.objects.filter(
98
+ content_type__app_label__in=["data", "graph", "documents"]
99
+ ),
100
+ *Permission.objects.filter(
101
+ content_type__app_label="audit", name__icontains="view"
102
+ ),
103
+ *Permission.objects.filter(
104
+ content_type__app_label="rest_framework_tracking",
105
+ name__icontains="view",
106
+ ),
107
+ ],
108
+ override=True,
109
+ )
110
+
111
+ # manage_shipments
112
+ setup_group(
113
+ serializers.PermissionGroup.manage_shipments.name,
114
+ permissions=[
115
+ *Permission.objects.filter(content_type__app_label="manager"),
116
+ *Permission.objects.filter(
117
+ models.Q(content_type__app_label="orgs")
118
+ & (
119
+ models.Q(name__icontains="address")
120
+ | models.Q(name__icontains="parcel")
121
+ | models.Q(name__icontains="commodity")
122
+ | models.Q(name__icontains="customs")
123
+ | models.Q(name__icontains="pickup")
124
+ | models.Q(name__icontains="tracker")
125
+ | models.Q(name__icontains="shipment")
126
+ )
127
+ ),
128
+ ],
129
+ )
130
+
131
+ # manage_system
132
+ setup_group(
133
+ serializers.PermissionGroup.manage_system.name,
134
+ permissions=Permission.objects.filter(
135
+ content_type__app_label__in=[
136
+ "admin",
137
+ "user",
138
+ "pricing",
139
+ "providers",
140
+ "audit",
141
+ "database",
142
+ "rest_framework_tracking",
143
+ ]
144
+ ),
145
+ )
146
+
147
+
148
+ def setup_group(
149
+ name: str, permissions: typing.List[Permission], override: bool = False
150
+ ):
151
+ group, created = users.Group.objects.get_or_create(name=name)
152
+
153
+ if created or override:
154
+ group.permissions.set(permissions)
155
+ group.save()
@@ -0,0 +1,54 @@
1
+ import typing
2
+ import karrio.lib as lib
3
+
4
+
5
+ class PermissionGroup(lib.StrEnum):
6
+ manage_apps = "manage_apps"
7
+ manage_team = "manage_team"
8
+ manage_system = "manage_system"
9
+ manage_orders = "manage_orders"
10
+ manage_data = "manage_data"
11
+ manage_pickups = "manage_pickups"
12
+ manage_carriers = "manage_carriers" # Deprecated: use read_carriers + write_carriers
13
+ read_carriers = "read_carriers"
14
+ write_carriers = "write_carriers"
15
+ manage_trackers = "manage_trackers"
16
+ manage_webhooks = "manage_webhooks"
17
+ manage_shipments = "manage_shipments"
18
+ manage_org_owner = "manage_org_owner"
19
+
20
+
21
+ PERMISSION_GROUPS = [(p.name, p.name) for p in list(PermissionGroup)]
22
+ ROLES_GROUPS: typing.Dict[str, typing.List[str]] = {
23
+ "owner": [
24
+ PermissionGroup.manage_org_owner.value,
25
+ PermissionGroup.manage_team.value,
26
+ PermissionGroup.manage_apps.value,
27
+ PermissionGroup.read_carriers.value,
28
+ PermissionGroup.write_carriers.value,
29
+ PermissionGroup.manage_webhooks.value,
30
+ PermissionGroup.manage_data.value,
31
+ PermissionGroup.manage_orders.value,
32
+ PermissionGroup.manage_pickups.value,
33
+ PermissionGroup.manage_trackers.value,
34
+ PermissionGroup.manage_shipments.value,
35
+ ],
36
+ "admin": [
37
+ PermissionGroup.manage_team.value,
38
+ PermissionGroup.manage_apps.value,
39
+ PermissionGroup.read_carriers.value,
40
+ PermissionGroup.write_carriers.value,
41
+ ],
42
+ "developer": [
43
+ PermissionGroup.manage_webhooks.value,
44
+ PermissionGroup.read_carriers.value,
45
+ ],
46
+ "member": [
47
+ PermissionGroup.manage_data.value,
48
+ PermissionGroup.manage_orders.value,
49
+ PermissionGroup.manage_pickups.value,
50
+ PermissionGroup.manage_trackers.value,
51
+ PermissionGroup.manage_shipments.value,
52
+ PermissionGroup.read_carriers.value,
53
+ ],
54
+ }
@@ -0,0 +1,18 @@
1
+ from django.db.models import signals
2
+
3
+ from karrio.server.core.logging import logger
4
+ import karrio.server.core.utils as utils
5
+ import karrio.server.user.models as user
6
+ import karrio.server.iam.models as models
7
+
8
+
9
+ def register_all():
10
+ signals.post_delete.connect(context_object_deleted, sender=user.Token)
11
+
12
+ logger.info("Signal registration complete", module="karrio.iam")
13
+
14
+
15
+ @utils.disable_for_loaddata
16
+ def context_object_deleted(sender, instance, *args, **kwargs):
17
+ # clean up permission contexts when related objects are removed.
18
+ models.ContextPermission.objects.filter(object_pk=instance.pk).delete()
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.