amsdal 0.5.34__cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.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 (276) hide show
  1. amsdal/Third-Party Materials - AMSDAL Dependencies - License Notices.md +1362 -0
  2. amsdal/__about__.py +4 -0
  3. amsdal/__about__.pyi +1 -0
  4. amsdal/__init__.py +23 -0
  5. amsdal/__init__.pyi +9 -0
  6. amsdal/__migrations__/0000_initial.py +36 -0
  7. amsdal/__migrations__/0001_create_class_file.py +61 -0
  8. amsdal/__migrations__/0002_create_class_file.py +109 -0
  9. amsdal/__migrations__/0003_update_class_file.py +91 -0
  10. amsdal/__migrations__/0004_update_class_file.py +45 -0
  11. amsdal/cloud/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
  12. amsdal/cloud/__init__.pyi +0 -0
  13. amsdal/cloud/client.cpython-313-x86_64-linux-gnu.so +0 -0
  14. amsdal/cloud/client.pyi +57 -0
  15. amsdal/cloud/constants.cpython-313-x86_64-linux-gnu.so +0 -0
  16. amsdal/cloud/constants.pyi +13 -0
  17. amsdal/cloud/enums.cpython-313-x86_64-linux-gnu.so +0 -0
  18. amsdal/cloud/enums.pyi +68 -0
  19. amsdal/cloud/models/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
  20. amsdal/cloud/models/__init__.pyi +0 -0
  21. amsdal/cloud/models/base.cpython-313-x86_64-linux-gnu.so +0 -0
  22. amsdal/cloud/models/base.pyi +247 -0
  23. amsdal/cloud/services/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
  24. amsdal/cloud/services/__init__.pyi +0 -0
  25. amsdal/cloud/services/actions/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
  26. amsdal/cloud/services/actions/__init__.pyi +0 -0
  27. amsdal/cloud/services/actions/add_allowlist_ip.cpython-313-x86_64-linux-gnu.so +0 -0
  28. amsdal/cloud/services/actions/add_allowlist_ip.pyi +19 -0
  29. amsdal/cloud/services/actions/add_basic_auth.cpython-313-x86_64-linux-gnu.so +0 -0
  30. amsdal/cloud/services/actions/add_basic_auth.pyi +21 -0
  31. amsdal/cloud/services/actions/add_dependency.cpython-313-x86_64-linux-gnu.so +0 -0
  32. amsdal/cloud/services/actions/add_dependency.pyi +19 -0
  33. amsdal/cloud/services/actions/add_secret.cpython-313-x86_64-linux-gnu.so +0 -0
  34. amsdal/cloud/services/actions/add_secret.pyi +20 -0
  35. amsdal/cloud/services/actions/base.cpython-313-x86_64-linux-gnu.so +0 -0
  36. amsdal/cloud/services/actions/base.pyi +122 -0
  37. amsdal/cloud/services/actions/create_deploy.cpython-313-x86_64-linux-gnu.so +0 -0
  38. amsdal/cloud/services/actions/create_deploy.pyi +41 -0
  39. amsdal/cloud/services/actions/create_env.cpython-313-x86_64-linux-gnu.so +0 -0
  40. amsdal/cloud/services/actions/create_env.pyi +19 -0
  41. amsdal/cloud/services/actions/create_session.cpython-313-x86_64-linux-gnu.so +0 -0
  42. amsdal/cloud/services/actions/create_session.pyi +17 -0
  43. amsdal/cloud/services/actions/delete_allowlist_ip.cpython-313-x86_64-linux-gnu.so +0 -0
  44. amsdal/cloud/services/actions/delete_allowlist_ip.pyi +19 -0
  45. amsdal/cloud/services/actions/delete_basic_auth.cpython-313-x86_64-linux-gnu.so +0 -0
  46. amsdal/cloud/services/actions/delete_basic_auth.pyi +20 -0
  47. amsdal/cloud/services/actions/delete_dependency.cpython-313-x86_64-linux-gnu.so +0 -0
  48. amsdal/cloud/services/actions/delete_dependency.pyi +21 -0
  49. amsdal/cloud/services/actions/delete_env.cpython-313-x86_64-linux-gnu.so +0 -0
  50. amsdal/cloud/services/actions/delete_env.pyi +21 -0
  51. amsdal/cloud/services/actions/delete_secret.cpython-313-x86_64-linux-gnu.so +0 -0
  52. amsdal/cloud/services/actions/delete_secret.pyi +21 -0
  53. amsdal/cloud/services/actions/destroy_deploy.cpython-313-x86_64-linux-gnu.so +0 -0
  54. amsdal/cloud/services/actions/destroy_deploy.pyi +18 -0
  55. amsdal/cloud/services/actions/expose_db.cpython-313-x86_64-linux-gnu.so +0 -0
  56. amsdal/cloud/services/actions/expose_db.pyi +22 -0
  57. amsdal/cloud/services/actions/get_basic_auth_credentials.cpython-313-x86_64-linux-gnu.so +0 -0
  58. amsdal/cloud/services/actions/get_basic_auth_credentials.pyi +21 -0
  59. amsdal/cloud/services/actions/get_monitoring_info.cpython-313-x86_64-linux-gnu.so +0 -0
  60. amsdal/cloud/services/actions/get_monitoring_info.pyi +21 -0
  61. amsdal/cloud/services/actions/list_dependencies.cpython-313-x86_64-linux-gnu.so +0 -0
  62. amsdal/cloud/services/actions/list_dependencies.pyi +21 -0
  63. amsdal/cloud/services/actions/list_deploys.cpython-313-x86_64-linux-gnu.so +0 -0
  64. amsdal/cloud/services/actions/list_deploys.pyi +19 -0
  65. amsdal/cloud/services/actions/list_envs.cpython-313-x86_64-linux-gnu.so +0 -0
  66. amsdal/cloud/services/actions/list_envs.pyi +20 -0
  67. amsdal/cloud/services/actions/list_secrets.cpython-313-x86_64-linux-gnu.so +0 -0
  68. amsdal/cloud/services/actions/list_secrets.pyi +22 -0
  69. amsdal/cloud/services/actions/manager.cpython-313-x86_64-linux-gnu.so +0 -0
  70. amsdal/cloud/services/actions/manager.pyi +278 -0
  71. amsdal/cloud/services/actions/signup_action.cpython-313-x86_64-linux-gnu.so +0 -0
  72. amsdal/cloud/services/actions/signup_action.pyi +20 -0
  73. amsdal/cloud/services/actions/update_deploy.cpython-313-x86_64-linux-gnu.so +0 -0
  74. amsdal/cloud/services/actions/update_deploy.pyi +19 -0
  75. amsdal/cloud/services/auth/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
  76. amsdal/cloud/services/auth/__init__.pyi +0 -0
  77. amsdal/cloud/services/auth/base.cpython-313-x86_64-linux-gnu.so +0 -0
  78. amsdal/cloud/services/auth/base.pyi +6 -0
  79. amsdal/cloud/services/auth/credentials.cpython-313-x86_64-linux-gnu.so +0 -0
  80. amsdal/cloud/services/auth/credentials.pyi +30 -0
  81. amsdal/cloud/services/auth/manager.cpython-313-x86_64-linux-gnu.so +0 -0
  82. amsdal/cloud/services/auth/manager.pyi +26 -0
  83. amsdal/cloud/services/auth/signup_service.cpython-313-x86_64-linux-gnu.so +0 -0
  84. amsdal/cloud/services/auth/signup_service.pyi +32 -0
  85. amsdal/cloud/services/auth/token.cpython-313-x86_64-linux-gnu.so +0 -0
  86. amsdal/cloud/services/auth/token.pyi +27 -0
  87. amsdal/configs/__init__.py +0 -0
  88. amsdal/configs/__init__.pyi +0 -0
  89. amsdal/configs/constants.py +33 -0
  90. amsdal/configs/constants.pyi +22 -0
  91. amsdal/configs/main.py +274 -0
  92. amsdal/configs/main.pyi +178 -0
  93. amsdal/context/__init__.py +0 -0
  94. amsdal/context/__init__.pyi +0 -0
  95. amsdal/context/manager.py +69 -0
  96. amsdal/context/manager.pyi +50 -0
  97. amsdal/contrib/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
  98. amsdal/contrib/__init__.pyi +0 -0
  99. amsdal/contrib/app_config.py +7 -0
  100. amsdal/contrib/app_config.pyi +6 -0
  101. amsdal/contrib/auth/__init__.py +0 -0
  102. amsdal/contrib/auth/__init__.pyi +0 -0
  103. amsdal/contrib/auth/app.py +27 -0
  104. amsdal/contrib/auth/app.pyi +15 -0
  105. amsdal/contrib/auth/decorators/__init__.py +35 -0
  106. amsdal/contrib/auth/decorators/__init__.pyi +6 -0
  107. amsdal/contrib/auth/errors.py +43 -0
  108. amsdal/contrib/auth/errors.pyi +16 -0
  109. amsdal/contrib/auth/fixtures/basic_permissions.json +64 -0
  110. amsdal/contrib/auth/lifecycle/__init__.py +0 -0
  111. amsdal/contrib/auth/lifecycle/__init__.pyi +0 -0
  112. amsdal/contrib/auth/lifecycle/consumer.py +394 -0
  113. amsdal/contrib/auth/lifecycle/consumer.pyi +108 -0
  114. amsdal/contrib/auth/migrations/0000_initial.py +87 -0
  115. amsdal/contrib/auth/migrations/0001_add_mfa_support.py +188 -0
  116. amsdal/contrib/auth/models/__init__.py +1 -0
  117. amsdal/contrib/auth/models/backup_code.py +85 -0
  118. amsdal/contrib/auth/models/email_mfa_device.py +108 -0
  119. amsdal/contrib/auth/models/login_session.py +235 -0
  120. amsdal/contrib/auth/models/mfa_device.py +86 -0
  121. amsdal/contrib/auth/models/permission.py +23 -0
  122. amsdal/contrib/auth/models/sms_device.py +113 -0
  123. amsdal/contrib/auth/models/totp_device.py +58 -0
  124. amsdal/contrib/auth/models/user.py +156 -0
  125. amsdal/contrib/auth/services/__init__.py +1 -0
  126. amsdal/contrib/auth/services/__init__.pyi +0 -0
  127. amsdal/contrib/auth/services/mfa_device_service.py +544 -0
  128. amsdal/contrib/auth/services/mfa_device_service.pyi +216 -0
  129. amsdal/contrib/auth/services/totp_service.py +358 -0
  130. amsdal/contrib/auth/services/totp_service.pyi +158 -0
  131. amsdal/contrib/auth/settings.py +44 -0
  132. amsdal/contrib/auth/settings.pyi +34 -0
  133. amsdal/contrib/auth/transactions/__init__.py +1 -0
  134. amsdal/contrib/auth/transactions/__init__.pyi +0 -0
  135. amsdal/contrib/auth/transactions/mfa_device_transactions.py +458 -0
  136. amsdal/contrib/auth/transactions/mfa_device_transactions.pyi +226 -0
  137. amsdal/contrib/auth/transactions/totp_transactions.py +203 -0
  138. amsdal/contrib/auth/transactions/totp_transactions.pyi +113 -0
  139. amsdal/contrib/auth/utils/__init__.py +0 -0
  140. amsdal/contrib/auth/utils/__init__.pyi +0 -0
  141. amsdal/contrib/auth/utils/mfa.py +257 -0
  142. amsdal/contrib/auth/utils/mfa.pyi +119 -0
  143. amsdal/contrib/frontend_configs/__init__.py +0 -0
  144. amsdal/contrib/frontend_configs/__init__.pyi +0 -0
  145. amsdal/contrib/frontend_configs/app.py +24 -0
  146. amsdal/contrib/frontend_configs/app.pyi +19 -0
  147. amsdal/contrib/frontend_configs/constants.py +1 -0
  148. amsdal/contrib/frontend_configs/constants.pyi +1 -0
  149. amsdal/contrib/frontend_configs/conversion/__init__.py +5 -0
  150. amsdal/contrib/frontend_configs/conversion/__init__.pyi +3 -0
  151. amsdal/contrib/frontend_configs/conversion/convert.py +310 -0
  152. amsdal/contrib/frontend_configs/conversion/convert.pyi +22 -0
  153. amsdal/contrib/frontend_configs/lifecycle/__init__.py +0 -0
  154. amsdal/contrib/frontend_configs/lifecycle/__init__.pyi +0 -0
  155. amsdal/contrib/frontend_configs/lifecycle/consumer.py +306 -0
  156. amsdal/contrib/frontend_configs/lifecycle/consumer.pyi +98 -0
  157. amsdal/contrib/frontend_configs/migrations/0000_initial.py +227 -0
  158. amsdal/contrib/frontend_configs/migrations/0001_update_frontend_control_config.py +245 -0
  159. amsdal/contrib/frontend_configs/migrations/0002_add_button_and_invoke_actions.py +352 -0
  160. amsdal/contrib/frontend_configs/migrations/0003_create_class_frontendconfigdashboardelement.py +145 -0
  161. amsdal/contrib/frontend_configs/models/__init__.py +0 -0
  162. amsdal/contrib/frontend_configs/models/frontend_activator_config.py +22 -0
  163. amsdal/contrib/frontend_configs/models/frontend_config_async_validator.py +11 -0
  164. amsdal/contrib/frontend_configs/models/frontend_config_control_action.py +110 -0
  165. amsdal/contrib/frontend_configs/models/frontend_config_dashboard.py +51 -0
  166. amsdal/contrib/frontend_configs/models/frontend_config_group_validator.py +21 -0
  167. amsdal/contrib/frontend_configs/models/frontend_config_option.py +12 -0
  168. amsdal/contrib/frontend_configs/models/frontend_config_skip_none_base.py +17 -0
  169. amsdal/contrib/frontend_configs/models/frontend_config_slider_option.py +13 -0
  170. amsdal/contrib/frontend_configs/models/frontend_config_text_mask.py +14 -0
  171. amsdal/contrib/frontend_configs/models/frontend_config_validator.py +28 -0
  172. amsdal/contrib/frontend_configs/models/frontend_control_config.py +110 -0
  173. amsdal/contrib/frontend_configs/models/frontend_model_config.py +14 -0
  174. amsdal/contrib/frontend_configs/utils.py +29 -0
  175. amsdal/contrib/frontend_configs/utils.pyi +17 -0
  176. amsdal/errors.py +31 -0
  177. amsdal/errors.pyi +12 -0
  178. amsdal/fixtures/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
  179. amsdal/fixtures/__init__.pyi +0 -0
  180. amsdal/fixtures/manager.cpython-313-x86_64-linux-gnu.so +0 -0
  181. amsdal/fixtures/manager.pyi +170 -0
  182. amsdal/fixtures/utils.cpython-313-x86_64-linux-gnu.so +0 -0
  183. amsdal/fixtures/utils.pyi +9 -0
  184. amsdal/manager.cpython-313-x86_64-linux-gnu.so +0 -0
  185. amsdal/manager.pyi +265 -0
  186. amsdal/mixins/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
  187. amsdal/mixins/__init__.pyi +0 -0
  188. amsdal/mixins/class_versions_mixin.cpython-313-x86_64-linux-gnu.so +0 -0
  189. amsdal/mixins/class_versions_mixin.pyi +12 -0
  190. amsdal/models/__init__.py +19 -0
  191. amsdal/models/core/__init__.py +0 -0
  192. amsdal/models/core/class_object.py +38 -0
  193. amsdal/models/core/class_property.py +26 -0
  194. amsdal/models/core/file.py +243 -0
  195. amsdal/models/core/fixture.py +25 -0
  196. amsdal/models/core/option.py +11 -0
  197. amsdal/models/core/storage_metadata.py +15 -0
  198. amsdal/models/core/validator.py +12 -0
  199. amsdal/models/mixins.py +31 -0
  200. amsdal/models/types/__init__.py +0 -0
  201. amsdal/models/types/object.py +26 -0
  202. amsdal/py.typed +0 -0
  203. amsdal/queryset/__init__.py +21 -0
  204. amsdal/queryset/__init__.pyi +6 -0
  205. amsdal/schemas/__init__.py +0 -0
  206. amsdal/schemas/__init__.pyi +0 -0
  207. amsdal/schemas/core/class_object/model.json +51 -0
  208. amsdal/schemas/core/class_object/properties/display_name.py +9 -0
  209. amsdal/schemas/core/class_property/model.json +41 -0
  210. amsdal/schemas/core/file/hooks/pre_create.py +24 -0
  211. amsdal/schemas/core/file/hooks/pre_update.py +24 -0
  212. amsdal/schemas/core/file/model.json +23 -0
  213. amsdal/schemas/core/file/properties/from_file.py +34 -0
  214. amsdal/schemas/core/file/properties/mimetype.py +13 -0
  215. amsdal/schemas/core/file/properties/str.py +6 -0
  216. amsdal/schemas/core/file/properties/to_file.py +24 -0
  217. amsdal/schemas/core/file/properties/validate_data.py +31 -0
  218. amsdal/schemas/core/fixture/model.json +35 -0
  219. amsdal/schemas/core/option/model.json +19 -0
  220. amsdal/schemas/core/storage_metadata/model.json +52 -0
  221. amsdal/schemas/core/validator/model.json +19 -0
  222. amsdal/schemas/interfaces.py +25 -0
  223. amsdal/schemas/interfaces.pyi +20 -0
  224. amsdal/schemas/manager.cpython-313-x86_64-linux-gnu.so +0 -0
  225. amsdal/schemas/manager.py +0 -0
  226. amsdal/schemas/manager.pyi +0 -0
  227. amsdal/schemas/mixins/__init__.py +0 -0
  228. amsdal/schemas/mixins/__init__.pyi +0 -0
  229. amsdal/schemas/mixins/check_dependencies_mixin.py +130 -0
  230. amsdal/schemas/mixins/check_dependencies_mixin.pyi +45 -0
  231. amsdal/schemas/mixins/verify_schemas_mixin.py +96 -0
  232. amsdal/schemas/mixins/verify_schemas_mixin.pyi +33 -0
  233. amsdal/schemas/repository.py +84 -0
  234. amsdal/schemas/repository.pyi +22 -0
  235. amsdal/schemas/types/anything/model.json +7 -0
  236. amsdal/schemas/types/array/model.json +7 -0
  237. amsdal/schemas/types/binary/model.json +7 -0
  238. amsdal/schemas/types/boolean/model.json +17 -0
  239. amsdal/schemas/types/date/model.json +7 -0
  240. amsdal/schemas/types/datetime/model.json +7 -0
  241. amsdal/schemas/types/dictionary/model.json +8 -0
  242. amsdal/schemas/types/number/model.json +8 -0
  243. amsdal/schemas/types/object/model.json +53 -0
  244. amsdal/schemas/types/string/model.json +8 -0
  245. amsdal/schemas/utils.py +16 -0
  246. amsdal/schemas/utils.pyi +10 -0
  247. amsdal/services/__init__.py +11 -0
  248. amsdal/services/__init__.pyi +4 -0
  249. amsdal/services/external_connections.py +262 -0
  250. amsdal/services/external_connections.pyi +190 -0
  251. amsdal/services/external_model_generator.py +350 -0
  252. amsdal/services/external_model_generator.pyi +134 -0
  253. amsdal/services/transaction_execution.cpython-313-x86_64-linux-gnu.so +0 -0
  254. amsdal/services/transaction_execution.pyi +93 -0
  255. amsdal/storages/__init__.py +20 -0
  256. amsdal/storages/__init__.pyi +8 -0
  257. amsdal/storages/file_system.py +214 -0
  258. amsdal/storages/file_system.pyi +36 -0
  259. amsdal/transactions/__init__.py +13 -0
  260. amsdal/transactions/__init__.pyi +4 -0
  261. amsdal/utils/__init__.py +0 -0
  262. amsdal/utils/__init__.pyi +0 -0
  263. amsdal/utils/contrib_paths.py +23 -0
  264. amsdal/utils/contrib_paths.pyi +14 -0
  265. amsdal/utils/rollback/__init__.py +440 -0
  266. amsdal/utils/rollback/__init__.pyi +38 -0
  267. amsdal/utils/tests/__init__.py +0 -0
  268. amsdal/utils/tests/enums.py +16 -0
  269. amsdal/utils/tests/factories.py +49 -0
  270. amsdal/utils/tests/helpers.py +331 -0
  271. amsdal/utils/tests/migrations.py +157 -0
  272. amsdal-0.5.34.dist-info/METADATA +375 -0
  273. amsdal-0.5.34.dist-info/RECORD +276 -0
  274. amsdal-0.5.34.dist-info/WHEEL +6 -0
  275. amsdal-0.5.34.dist-info/licenses/LICENSE.txt +107 -0
  276. amsdal-0.5.34.dist-info/top_level.txt +1 -0
@@ -0,0 +1,394 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import jwt
5
+ from amsdal_data.transactions.decorators import async_transaction
6
+ from amsdal_data.transactions.decorators import transaction
7
+ from amsdal_models.classes.helpers.reference_loader import ReferenceLoader
8
+ from amsdal_models.classes.model import Model
9
+ from amsdal_utils.lifecycle.consumer import LifecycleConsumer
10
+ from amsdal_utils.models.data_models.reference import Reference
11
+ from amsdal_utils.models.enums import Versions
12
+
13
+ from amsdal.contrib.auth.errors import AuthenticationError
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class CheckAndCreateSuperUserConsumer(LifecycleConsumer):
19
+ """
20
+ Ensures the existence of a super user in the system.
21
+
22
+ This consumer checks if a super user exists based on the provided email and password
23
+ in the authentication settings. If the super user does not exist, it creates one.
24
+ """
25
+
26
+ @transaction
27
+ def on_event(self) -> None:
28
+ """
29
+ Checks for the existence of a super user and creates one if necessary.
30
+
31
+ This method ensures that a super user exists by checking the email and password
32
+ in the authentication settings. If the super user does not exist, it creates one
33
+ with the necessary permissions.
34
+ """
35
+ from amsdal.contrib.auth.models.permission import Permission
36
+ from amsdal.contrib.auth.models.user import User
37
+ from amsdal.contrib.auth.settings import auth_settings
38
+
39
+ logger.info('Ensure super user exists')
40
+
41
+ if not (auth_settings.ADMIN_USER_EMAIL and auth_settings.ADMIN_USER_PASSWORD):
42
+ logger.info('Email / password missing for super user - skipping')
43
+ return
44
+
45
+ user = (
46
+ User.objects.filter(email=auth_settings.ADMIN_USER_EMAIL, _address__object_version=Versions.LATEST)
47
+ .get_or_none()
48
+ .execute()
49
+ )
50
+ if user is not None:
51
+ logger.info('Super user already exists - skipping')
52
+ return
53
+
54
+ logger.info("Super user doesn't exist - creating now")
55
+
56
+ access_all_permission = (
57
+ Permission.objects.filter(
58
+ model='*',
59
+ action='*',
60
+ _address__object_version=Versions.LATEST,
61
+ )
62
+ .get()
63
+ .execute()
64
+ )
65
+
66
+ instance = User( # type: ignore[call-arg]
67
+ email=auth_settings.ADMIN_USER_EMAIL,
68
+ password=auth_settings.ADMIN_USER_PASSWORD.encode(),
69
+ permissions=[access_all_permission],
70
+ )
71
+ instance.save(force_insert=True)
72
+ logger.info('Super user created successfully')
73
+
74
+ @async_transaction
75
+ async def on_event_async(self) -> None: # type: ignore[override]
76
+ """
77
+ Checks for the existence of a super user and creates one if necessary.
78
+
79
+ This method ensures that a super user exists by checking the email and password
80
+ in the authentication settings. If the super user does not exist, it creates one
81
+ with the necessary permissions.
82
+ """
83
+ from amsdal.contrib.auth.models.permission import Permission # type: ignore[import-not-found]
84
+ from amsdal.contrib.auth.models.user import User # type: ignore[import-not-found]
85
+ from amsdal.contrib.auth.settings import auth_settings
86
+
87
+ logger.info('Ensure super user exists')
88
+
89
+ if not (auth_settings.ADMIN_USER_EMAIL and auth_settings.ADMIN_USER_PASSWORD):
90
+ logger.info('Email / password missing for super user - skipping')
91
+ return
92
+
93
+ user = (
94
+ await User.objects.filter(email=auth_settings.ADMIN_USER_EMAIL, _address__object_version=Versions.LATEST)
95
+ .get_or_none()
96
+ .aexecute()
97
+ )
98
+ if user is not None:
99
+ logger.info('Super user already exists - skipping')
100
+ return
101
+
102
+ logger.info("Super user doesn't exist - creating now")
103
+
104
+ access_all_permission = (
105
+ await Permission.objects.filter(
106
+ model='*',
107
+ action='*',
108
+ _address__object_version=Versions.LATEST,
109
+ )
110
+ .get()
111
+ .aexecute()
112
+ )
113
+
114
+ instance = User( # type: ignore[call-arg]
115
+ email=auth_settings.ADMIN_USER_EMAIL,
116
+ password=auth_settings.ADMIN_USER_PASSWORD.encode(),
117
+ permissions=[access_all_permission],
118
+ )
119
+ await instance.asave(force_insert=True) # type: ignore[misc]
120
+ logger.info('Super user created successfully')
121
+
122
+
123
+ class AuthenticateUserConsumer(LifecycleConsumer):
124
+ """
125
+ Authenticates a user based on a provided JWT token.
126
+
127
+ This consumer decodes the JWT token from the authorization header and retrieves
128
+ the corresponding user from the database. If the token is invalid or expired,
129
+ it raises an `AuthenticationError`.
130
+ """
131
+
132
+ def on_event(self, auth_header: str, authentication_info: Any) -> None:
133
+ """
134
+ Authenticates the user using the provided JWT token.
135
+
136
+ This method decodes the JWT token from the authorization header and retrieves
137
+ the corresponding user from the database. If the token is invalid or expired,
138
+ it raises an `AuthenticationError`.
139
+
140
+ Args:
141
+ auth_header (str): The JWT token from the authorization header.
142
+ authentication_info (Any): The authentication information object to update with the user.
143
+ """
144
+ from amsdal.contrib.auth.models.user import User # type: ignore[import-not-found]
145
+ from amsdal.contrib.auth.settings import auth_settings
146
+
147
+ authentication_info.user = None
148
+ email: str | None
149
+
150
+ try:
151
+ jwt_payload = jwt.decode(
152
+ auth_header,
153
+ auth_settings.AUTH_JWT_KEY, # type: ignore[arg-type]
154
+ algorithms=['HS256'],
155
+ )
156
+ email = jwt_payload['email']
157
+ except jwt.ExpiredSignatureError as exc:
158
+ logger.error('Auth token expired. Defaulting to anonymous user.')
159
+
160
+ msg = 'Auth token has expired.'
161
+ raise AuthenticationError(msg) from exc
162
+ except Exception as exc:
163
+ logger.error('Auth token decode failure. Defaulting to anonymous user.')
164
+
165
+ msg = 'Failed to decode auth token.'
166
+ raise AuthenticationError(msg) from exc
167
+
168
+ user = User.objects.filter(email=email, _address__object_version=Versions.LATEST).get_or_none().execute()
169
+
170
+ authentication_info.user = user
171
+
172
+ async def on_event_async(self, auth_header: str, authentication_info: Any) -> None:
173
+ """
174
+ Authenticates the user using the provided JWT token.
175
+
176
+ This method decodes the JWT token from the authorization header and retrieves
177
+ the corresponding user from the database. If the token is invalid or expired,
178
+ it raises an `AuthenticationError`.
179
+
180
+ Args:
181
+ auth_header (str): The JWT token from the authorization header.
182
+ authentication_info (Any): The authentication information object to update with the user.
183
+ """
184
+ from amsdal.contrib.auth.models.user import User # type: ignore[import-not-found]
185
+ from amsdal.contrib.auth.settings import auth_settings
186
+
187
+ authentication_info.user = None
188
+ email: str | None
189
+
190
+ try:
191
+ jwt_payload = jwt.decode(
192
+ auth_header,
193
+ auth_settings.AUTH_JWT_KEY, # type: ignore[arg-type]
194
+ algorithms=['HS256'],
195
+ )
196
+ email = jwt_payload['email']
197
+ except jwt.ExpiredSignatureError as exc:
198
+ logger.error('Auth token expired. Defaulting to anonymous user.')
199
+
200
+ msg = 'Auth token has expired.'
201
+ raise AuthenticationError(msg) from exc
202
+ except Exception as exc:
203
+ logger.error('Auth token decode failure. Defaulting to anonymous user.')
204
+
205
+ msg = 'Failed to decode auth token.'
206
+ raise AuthenticationError(msg) from exc
207
+
208
+ user = await User.objects.filter(email=email, _address__object_version=Versions.LATEST).get_or_none().aexecute()
209
+
210
+ authentication_info.user = user
211
+
212
+
213
+ class CheckPermissionConsumer(LifecycleConsumer):
214
+ """
215
+ Checks and manages permissions for a given user and object.
216
+
217
+ This consumer prepopulates default permissions, checks class-level permissions,
218
+ and object-level permissions for a given user and object.
219
+ """
220
+
221
+ def _prepopulate_default_permissions(self, object_class: type[Model], permissions_info: Any) -> None:
222
+ from amsdal.contrib.auth.models.permission import Permission
223
+ from amsdal.contrib.auth.settings import auth_settings
224
+
225
+ permissions_info.has_read_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
226
+ permissions_info.has_create_permission = (
227
+ (not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION) if object_class.__name__ != 'LoginSession' else True
228
+ )
229
+ permissions_info.has_update_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
230
+ permissions_info.has_delete_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
231
+
232
+ required_permissions = Permission.objects.filter(
233
+ model=object_class.__name__,
234
+ _address__object_version=Versions.LATEST,
235
+ ).execute()
236
+
237
+ for required_permission in required_permissions:
238
+ if required_permission.action == 'read':
239
+ permissions_info.has_read_permission = False
240
+ elif required_permission.action == 'create':
241
+ permissions_info.has_create_permission = False
242
+ elif required_permission.action == 'update':
243
+ permissions_info.has_update_permission = False
244
+ elif required_permission.action == 'delete':
245
+ permissions_info.has_delete_permission = False
246
+
247
+ async def _async_prepopulate_default_permissions(self, object_class: type[Model], permissions_info: Any) -> None:
248
+ from amsdal.contrib.auth.models.permission import Permission # type: ignore[import-not-found]
249
+ from amsdal.contrib.auth.settings import auth_settings
250
+
251
+ permissions_info.has_read_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
252
+ permissions_info.has_create_permission = (
253
+ (not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION) if object_class.__name__ != 'LoginSession' else True
254
+ )
255
+ permissions_info.has_update_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
256
+ permissions_info.has_delete_permission = not auth_settings.REQUIRE_DEFAULT_AUTHORIZATION
257
+
258
+ required_permissions = await Permission.objects.filter(
259
+ model=object_class.__name__,
260
+ _address__object_version=Versions.LATEST,
261
+ ).aexecute()
262
+
263
+ for required_permission in required_permissions:
264
+ if required_permission.action == 'read':
265
+ permissions_info.has_read_permission = False
266
+ elif required_permission.action == 'create':
267
+ permissions_info.has_create_permission = False
268
+ elif required_permission.action == 'update':
269
+ permissions_info.has_update_permission = False
270
+ elif required_permission.action == 'delete':
271
+ permissions_info.has_delete_permission = False
272
+
273
+ def _check_class_permissions(self, object_class: type[Model], user: Any, permissions_info: Any) -> None:
274
+ if hasattr(object_class, 'has_permission'):
275
+ for action in ['read', 'create', 'update', 'delete']:
276
+ setattr(permissions_info, f'has_{action}_permission', object_class.has_permission(user, action))
277
+
278
+ if not user or not getattr(user, 'permissions', None):
279
+ return
280
+
281
+ user_permissions = [
282
+ ReferenceLoader(p).load_reference() if isinstance(p, Reference) else p for p in user.permissions
283
+ ]
284
+
285
+ for user_permission in user_permissions:
286
+ if user_permission.model not in [object_class.__name__, '*']:
287
+ continue
288
+
289
+ if user_permission.action == 'read':
290
+ permissions_info.has_read_permission = True
291
+ elif user_permission.action == 'create':
292
+ permissions_info.has_create_permission = True
293
+ elif user_permission.action == 'update':
294
+ permissions_info.has_update_permission = True
295
+ elif user_permission.action == 'delete':
296
+ permissions_info.has_delete_permission = True
297
+ elif user_permission.action == '*':
298
+ permissions_info.has_read_permission = True
299
+ permissions_info.has_create_permission = True
300
+ permissions_info.has_update_permission = True
301
+ permissions_info.has_delete_permission = True
302
+
303
+ async def _async_check_class_permissions(self, object_class: type[Model], user: Any, permissions_info: Any) -> None:
304
+ if hasattr(object_class, 'has_permission'):
305
+ for action in ['read', 'create', 'update', 'delete']:
306
+ setattr(permissions_info, f'has_{action}_permission', object_class.has_permission(user, action))
307
+
308
+ if not user or not getattr(user, 'permissions', None):
309
+ return
310
+
311
+ user_permissions = [
312
+ await ReferenceLoader(p).aload_reference() if isinstance(p, Reference) else p for p in user.permissions
313
+ ]
314
+
315
+ for user_permission in user_permissions:
316
+ if user_permission.model not in [object_class.__name__, '*']:
317
+ continue
318
+
319
+ if user_permission.action == 'read':
320
+ permissions_info.has_read_permission = True
321
+ elif user_permission.action == 'create':
322
+ permissions_info.has_create_permission = True
323
+ elif user_permission.action == 'update':
324
+ permissions_info.has_update_permission = True
325
+ elif user_permission.action == 'delete':
326
+ permissions_info.has_delete_permission = True
327
+ elif user_permission.action == '*':
328
+ permissions_info.has_read_permission = True
329
+ permissions_info.has_create_permission = True
330
+ permissions_info.has_update_permission = True
331
+ permissions_info.has_delete_permission = True
332
+
333
+ def _check_object_permissions(self, obj: Model, user: Any, permissions_info: Any) -> None:
334
+ if hasattr(obj, 'has_object_permission'):
335
+ for action in ['read', 'update', 'delete']:
336
+ setattr(
337
+ permissions_info,
338
+ f'has_{action}_permission',
339
+ getattr(permissions_info, f'has_{action}_permission') and obj.has_object_permission(user, action),
340
+ )
341
+
342
+ def on_event(
343
+ self,
344
+ object_class: type[Model],
345
+ user: Any,
346
+ access_types: list[Any], # noqa: ARG002
347
+ permissions_info: Any,
348
+ obj: Model | None = None,
349
+ ) -> None:
350
+ """
351
+ Main method to check permissions for a given user and object.
352
+
353
+ This method prepopulates default permissions, checks class-level permissions,
354
+ and object-level permissions for the given user and object.
355
+
356
+ Args:
357
+ object_class (type[Model]): The class of the object to check permissions for.
358
+ user (Any): The user to check permissions for.
359
+ access_types (list[Any]): The list of access types to check.
360
+ permissions_info (Any): The permissions information object to update.
361
+ obj (Model | None): The object to check permissions for, if any.
362
+ """
363
+ self._prepopulate_default_permissions(object_class, permissions_info)
364
+ self._check_class_permissions(object_class, user, permissions_info)
365
+
366
+ if obj:
367
+ self._check_object_permissions(obj, user, permissions_info)
368
+
369
+ async def on_event_async(
370
+ self,
371
+ object_class: type[Model],
372
+ user: Any,
373
+ access_types: list[Any], # noqa: ARG002
374
+ permissions_info: Any,
375
+ obj: Model | None = None,
376
+ ) -> None:
377
+ """
378
+ Main method to check permissions for a given user and object.
379
+
380
+ This method prepopulates default permissions, checks class-level permissions,
381
+ and object-level permissions for the given user and object.
382
+
383
+ Args:
384
+ object_class (type[Model]): The class of the object to check permissions for.
385
+ user (Any): The user to check permissions for.
386
+ access_types (list[Any]): The list of access types to check.
387
+ permissions_info (Any): The permissions information object to update.
388
+ obj (Model | None): The object to check permissions for, if any.
389
+ """
390
+ await self._async_prepopulate_default_permissions(object_class, permissions_info)
391
+ await self._async_check_class_permissions(object_class, user, permissions_info)
392
+
393
+ if obj:
394
+ self._check_object_permissions(obj, user, permissions_info)
@@ -0,0 +1,108 @@
1
+ from _typeshed import Incomplete
2
+ from amsdal.contrib.auth.errors import AuthenticationError as AuthenticationError
3
+ from amsdal_data.transactions.decorators import async_transaction, transaction
4
+ from amsdal_models.classes.model import Model
5
+ from amsdal_utils.lifecycle.consumer import LifecycleConsumer
6
+ from typing import Any
7
+
8
+ logger: Incomplete
9
+
10
+ class CheckAndCreateSuperUserConsumer(LifecycleConsumer):
11
+ """
12
+ Ensures the existence of a super user in the system.
13
+
14
+ This consumer checks if a super user exists based on the provided email and password
15
+ in the authentication settings. If the super user does not exist, it creates one.
16
+ """
17
+ @transaction
18
+ def on_event(self) -> None:
19
+ """
20
+ Checks for the existence of a super user and creates one if necessary.
21
+
22
+ This method ensures that a super user exists by checking the email and password
23
+ in the authentication settings. If the super user does not exist, it creates one
24
+ with the necessary permissions.
25
+ """
26
+ @async_transaction
27
+ async def on_event_async(self) -> None:
28
+ """
29
+ Checks for the existence of a super user and creates one if necessary.
30
+
31
+ This method ensures that a super user exists by checking the email and password
32
+ in the authentication settings. If the super user does not exist, it creates one
33
+ with the necessary permissions.
34
+ """
35
+
36
+ class AuthenticateUserConsumer(LifecycleConsumer):
37
+ """
38
+ Authenticates a user based on a provided JWT token.
39
+
40
+ This consumer decodes the JWT token from the authorization header and retrieves
41
+ the corresponding user from the database. If the token is invalid or expired,
42
+ it raises an `AuthenticationError`.
43
+ """
44
+ def on_event(self, auth_header: str, authentication_info: Any) -> None:
45
+ """
46
+ Authenticates the user using the provided JWT token.
47
+
48
+ This method decodes the JWT token from the authorization header and retrieves
49
+ the corresponding user from the database. If the token is invalid or expired,
50
+ it raises an `AuthenticationError`.
51
+
52
+ Args:
53
+ auth_header (str): The JWT token from the authorization header.
54
+ authentication_info (Any): The authentication information object to update with the user.
55
+ """
56
+ async def on_event_async(self, auth_header: str, authentication_info: Any) -> None:
57
+ """
58
+ Authenticates the user using the provided JWT token.
59
+
60
+ This method decodes the JWT token from the authorization header and retrieves
61
+ the corresponding user from the database. If the token is invalid or expired,
62
+ it raises an `AuthenticationError`.
63
+
64
+ Args:
65
+ auth_header (str): The JWT token from the authorization header.
66
+ authentication_info (Any): The authentication information object to update with the user.
67
+ """
68
+
69
+ class CheckPermissionConsumer(LifecycleConsumer):
70
+ """
71
+ Checks and manages permissions for a given user and object.
72
+
73
+ This consumer prepopulates default permissions, checks class-level permissions,
74
+ and object-level permissions for a given user and object.
75
+ """
76
+ def _prepopulate_default_permissions(self, object_class: type[Model], permissions_info: Any) -> None: ...
77
+ async def _async_prepopulate_default_permissions(self, object_class: type[Model], permissions_info: Any) -> None: ...
78
+ def _check_class_permissions(self, object_class: type[Model], user: Any, permissions_info: Any) -> None: ...
79
+ async def _async_check_class_permissions(self, object_class: type[Model], user: Any, permissions_info: Any) -> None: ...
80
+ def _check_object_permissions(self, obj: Model, user: Any, permissions_info: Any) -> None: ...
81
+ def on_event(self, object_class: type[Model], user: Any, access_types: list[Any], permissions_info: Any, obj: Model | None = None) -> None:
82
+ """
83
+ Main method to check permissions for a given user and object.
84
+
85
+ This method prepopulates default permissions, checks class-level permissions,
86
+ and object-level permissions for the given user and object.
87
+
88
+ Args:
89
+ object_class (type[Model]): The class of the object to check permissions for.
90
+ user (Any): The user to check permissions for.
91
+ access_types (list[Any]): The list of access types to check.
92
+ permissions_info (Any): The permissions information object to update.
93
+ obj (Model | None): The object to check permissions for, if any.
94
+ """
95
+ async def on_event_async(self, object_class: type[Model], user: Any, access_types: list[Any], permissions_info: Any, obj: Model | None = None) -> None:
96
+ """
97
+ Main method to check permissions for a given user and object.
98
+
99
+ This method prepopulates default permissions, checks class-level permissions,
100
+ and object-level permissions for the given user and object.
101
+
102
+ Args:
103
+ object_class (type[Model]): The class of the object to check permissions for.
104
+ user (Any): The user to check permissions for.
105
+ access_types (list[Any]): The list of access types to check.
106
+ permissions_info (Any): The permissions information object to update.
107
+ obj (Model | None): The object to check permissions for, if any.
108
+ """
@@ -0,0 +1,87 @@
1
+ from amsdal_models.migration import migrations
2
+ from amsdal_utils.models.enums import ModuleType
3
+
4
+
5
+ class Migration(migrations.Migration):
6
+ operations: list[migrations.Operation] = [
7
+ migrations.CreateClass(
8
+ module_type=ModuleType.CONTRIB,
9
+ class_name="LoginSession",
10
+ new_schema={
11
+ "title": "LoginSession",
12
+ "required": ["email", "password"],
13
+ "properties": {
14
+ "email": {"type": "string", "title": "Email"},
15
+ "password": {"type": "string", "title": "Password (hash)"},
16
+ "token": {"type": "string", "title": "Token"},
17
+ },
18
+ "custom_code": "from datetime import UTC\nfrom datetime import datetime\nfrom datetime import timedelta\nfrom typing import Any\n\nimport jwt\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"\n Returns the display name of the user.\n\n This method returns the email of the user as their display name.\n\n Returns:\n str: The email of the user.\n \"\"\"\n return self.email\n\nasync def apre_create(self) -> None:\n import bcrypt\n\n from amsdal.contrib.auth.errors import AuthenticationError\n from amsdal.contrib.auth.models.user import User\n user = await User.objects.filter(email=self.email).latest().first().aexecute()\n if not user:\n msg = 'User not found'\n raise AuthenticationError(msg)\n if not bcrypt.checkpw(self.password.encode(), user.password):\n msg = 'Invalid password'\n raise AuthenticationError(msg)\n self.password = 'validated'\n\nasync def apre_update(self) -> None:\n from amsdal.contrib.auth.errors import AuthenticationError\n msg = 'Update not allowed'\n raise AuthenticationError(msg)\n\ndef pre_create(self) -> None:\n import bcrypt\n\n from amsdal.contrib.auth.errors import AuthenticationError\n from amsdal.contrib.auth.models.user import User\n user = User.objects.filter(email=self.email).latest().first().execute()\n if not user:\n msg = 'User not found'\n raise AuthenticationError(msg)\n if not bcrypt.checkpw(self.password.encode(), user.password):\n msg = 'Invalid password'\n raise AuthenticationError(msg)\n self.password = 'validated'\n\ndef pre_init(self, *, is_new_object: bool, kwargs: dict[str, Any]) -> None:\n \"\"\"\n Pre-initializes a user object by validating email and password, and generating a JWT token.\n\n This method checks if the object is new and validates the provided email and password.\n If the email and password are valid, it generates a JWT token and adds it to the kwargs.\n\n Args:\n is_new_object (bool): Indicates if the object is new.\n kwargs (dict[str, Any]): The keyword arguments containing user details.\n\n Raises:\n AuthenticationError: If the email or password is invalid.\n \"\"\"\n if not is_new_object or '_metadata' in kwargs:\n return\n from amsdal.contrib.auth.errors import AuthenticationError\n from amsdal.contrib.auth.settings import auth_settings\n email = kwargs.get('email', None)\n password = kwargs.get('password', None)\n if not email:\n msg = \"Email can't be empty\"\n raise AuthenticationError(msg)\n if not password:\n msg = \"Password can't be empty\"\n raise AuthenticationError(msg)\n lowercased_email = email.lower()\n if not auth_settings.AUTH_JWT_KEY:\n msg = 'JWT key is not set'\n raise AuthenticationError(msg)\n expiration_time = datetime.now(tz=UTC) + timedelta(seconds=auth_settings.AUTH_TOKEN_EXPIRATION)\n token = jwt.encode({'email': lowercased_email, 'exp': expiration_time}, key=auth_settings.AUTH_JWT_KEY, algorithm='HS256')\n kwargs['token'] = token\n\ndef pre_update(self) -> None:\n from amsdal.contrib.auth.errors import AuthenticationError\n msg = 'Update not allowed'\n raise AuthenticationError(msg)",
19
+ "storage_metadata": {
20
+ "table_name": "LoginSession",
21
+ "db_fields": {},
22
+ "primary_key": ["partition_key"],
23
+ "foreign_keys": {},
24
+ },
25
+ },
26
+ ),
27
+ migrations.CreateClass(
28
+ module_type=ModuleType.CONTRIB,
29
+ class_name="Permission",
30
+ new_schema={
31
+ "title": "Permission",
32
+ "required": ["model", "action"],
33
+ "properties": {
34
+ "model": {"type": "string", "title": "Model"},
35
+ "action": {"type": "string", "title": "Action"},
36
+ },
37
+ "custom_code": '@property\ndef display_name(self) -> str:\n """\n Returns the display name of the user.\n\n This method returns a formatted string combining the model and action of the user.\n\n Returns:\n str: The formatted display name in the format \'model:action\'.\n """\n return f\'{self.model}:{self.action}\'',
38
+ "storage_metadata": {
39
+ "table_name": "Permission",
40
+ "db_fields": {},
41
+ "primary_key": ["partition_key"],
42
+ "foreign_keys": {},
43
+ },
44
+ },
45
+ ),
46
+ migrations.CreateClass(
47
+ module_type=ModuleType.CONTRIB,
48
+ class_name="User",
49
+ new_schema={
50
+ "title": "User",
51
+ "required": ["email", "password"],
52
+ "properties": {
53
+ "email": {"type": "string", "title": "Email"},
54
+ "password": {"type": "binary", "title": "Password (hash)"},
55
+ "permissions": {"type": "array", "items": {"type": "Permission"}, "title": "Permissions"},
56
+ },
57
+ "custom_code": "from typing import Any\n\nfrom amsdal.contrib.auth.models.permission import *\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"\n Returns the display name of the user.\n\n This method returns the email of the user as their display name.\n\n Returns:\n str: The email of the user.\n \"\"\"\n return self.email\n\nasync def apre_update(self) -> None:\n import bcrypt\n original_object = await self.arefetch_from_db()\n password = self.password\n if original_object.password and password is not None:\n if isinstance(password, str):\n password = password.encode('utf-8')\n try:\n if not bcrypt.checkpw(password, original_object.password):\n self.password = password\n except ValueError:\n hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())\n self.password = hashed_password\n\ndef __repr__(self) -> str:\n return str(self)\n\ndef __str__(self) -> str:\n return f'User(email={self.email})'\n\ndef post_init(self, *, is_new_object: bool, kwargs: dict[str, Any]) -> None:\n \"\"\"\n Post-initializes a user object by validating email and password, and hashing the password.\n\n This method checks if the email and password are provided and valid. If the object is new,\n it hashes the password and sets the object ID to the lowercased email.\n\n Args:\n is_new_object (bool): Indicates if the object is new.\n kwargs (dict[str, Any]): The keyword arguments containing user details.\n\n Raises:\n UserCreationError: If the email or password is invalid.\n \"\"\"\n import bcrypt\n\n from amsdal.contrib.auth.errors import UserCreationError\n email = kwargs.get('email', None)\n password = kwargs.get('password', None)\n if email is None or email == '':\n msg = \"Email can't be empty\"\n raise UserCreationError(msg)\n if password is None or password == '':\n msg = \"Password can't be empty\"\n raise UserCreationError(msg)\n kwargs['email'] = email.lower()\n if is_new_object and '_metadata' not in kwargs:\n if isinstance(password, str):\n password = password.encode('utf-8')\n hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())\n self.password = hashed_password\n self._object_id = email.lower()\n\ndef pre_create(self) -> None:\n \"\"\"\n Pre-creates a user object.\n\n This method is a placeholder for any pre-creation logic that needs to be executed\n before a user object is created.\n \"\"\"\n pass\n\ndef pre_update(self) -> None:\n import bcrypt\n original_object = self.refetch_from_db()\n password = self.password\n if original_object.password and password is not None:\n if isinstance(password, str):\n password = password.encode('utf-8')\n try:\n if not bcrypt.checkpw(password, original_object.password):\n self.password = password\n except ValueError:\n hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())\n self.password = hashed_password",
58
+ "storage_metadata": {
59
+ "table_name": "User",
60
+ "db_fields": {},
61
+ "primary_key": ["partition_key"],
62
+ "foreign_keys": {},
63
+ },
64
+ },
65
+ ),
66
+ migrations.CreateClass(
67
+ module_type=ModuleType.CONTRIB,
68
+ class_name="UserPermission",
69
+ new_schema={
70
+ "title": "UserPermission",
71
+ "required": ["user", "permission"],
72
+ "properties": {
73
+ "user": {"type": "User", "title": "User"},
74
+ "permission": {"type": "Permission", "title": "Permission"},
75
+ },
76
+ "storage_metadata": {
77
+ "table_name": "UserPermission",
78
+ "db_fields": {"user": ["user_partition_key"], "permission": ["permission_partition_key"]},
79
+ "primary_key": ["user", "permission"],
80
+ "foreign_keys": {
81
+ "user": [{"user_partition_key": "string"}, "User", ["partition_key"]],
82
+ "permission": [{"permission_partition_key": "string"}, "Permission", ["partition_key"]],
83
+ },
84
+ },
85
+ },
86
+ ),
87
+ ]