oxutils 0.3.2__tar.gz → 0.4.0__tar.gz

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 (217) hide show
  1. {oxutils-0.3.2 → oxutils-0.4.0}/PKG-INFO +1 -1
  2. {oxutils-0.3.2 → oxutils-0.4.0}/pyproject.toml +1 -1
  3. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/__init__.py +1 -1
  4. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/invitations/backend.py +10 -13
  5. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/invitations/controllers.py +21 -18
  6. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/invitations/schemas.py +18 -11
  7. oxutils-0.4.0/src/oxutils/jwt/models.py +24 -0
  8. oxutils-0.4.0/src/oxutils/jwt/tokens.py +3 -0
  9. oxutils-0.4.0/src/oxutils/oxiliere/cacheops.py +47 -0
  10. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/middleware.py +43 -95
  11. oxutils-0.4.0/src/oxutils/oxiliere/permissions.py +97 -0
  12. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/settings.py +0 -4
  13. oxutils-0.3.2/src/oxutils/jwt/models.py +0 -177
  14. oxutils-0.3.2/src/oxutils/jwt/tokens.py +0 -77
  15. oxutils-0.3.2/src/oxutils/oxiliere/cacheops.py +0 -7
  16. oxutils-0.3.2/src/oxutils/oxiliere/permissions.py +0 -114
  17. {oxutils-0.3.2 → oxutils-0.4.0}/README.md +0 -0
  18. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/apps.py +0 -0
  19. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/__init__.py +0 -0
  20. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/apps.py +0 -0
  21. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/export.py +0 -0
  22. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/locale/fr/LC_MESSAGES/django.mo +0 -0
  23. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/locale/fr/LC_MESSAGES/django.po +0 -0
  24. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/masks.py +0 -0
  25. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/migrations/0001_initial.py +0 -0
  26. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/migrations/__init__.py +0 -0
  27. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/models.py +0 -0
  28. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/settings.py +0 -0
  29. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/audit/utils.py +0 -0
  30. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/__init__.py +0 -0
  31. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/adapter.py +0 -0
  32. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/admin.py +0 -0
  33. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/app_settings.py +0 -0
  34. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/apps.py +0 -0
  35. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/constants.py +0 -0
  36. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/controllers.py +0 -0
  37. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/emails/__init__.py +0 -0
  38. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/emails/controllers.py +0 -0
  39. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/emails/schemas.py +0 -0
  40. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/emails/services.py +0 -0
  41. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/exceptions.py +0 -0
  42. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/invitations/__init__.py +0 -0
  43. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/invitations/models.py +0 -0
  44. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/invitations/tokens.py +0 -0
  45. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/__init__.py +0 -0
  46. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/adapter.py +0 -0
  47. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/base/__init__.py +0 -0
  48. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/base/controllers.py +0 -0
  49. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/base/schemas.py +0 -0
  50. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/recovery_codes/__init__.py +0 -0
  51. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/recovery_codes/controllers.py +0 -0
  52. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/recovery_codes/flows.py +0 -0
  53. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/recovery_codes/schemas.py +0 -0
  54. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/recovery_codes/services.py +0 -0
  55. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/totp/__ini__.py +0 -0
  56. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/totp/controllers.py +0 -0
  57. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/totp/flows.py +0 -0
  58. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/totp/permissions.py +0 -0
  59. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/totp/schemas.py +0 -0
  60. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/totp/services.py +0 -0
  61. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mfa/utils.py +0 -0
  62. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/migrations/0001_initial.py +0 -0
  63. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/migrations/__init__.py +0 -0
  64. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/mixins.py +0 -0
  65. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/models.py +0 -0
  66. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/password_reset/__init__.py +0 -0
  67. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/password_reset/controllers.py +0 -0
  68. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/password_reset/models.py +0 -0
  69. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/password_reset/permissions.py +0 -0
  70. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/password_reset/schemas.py +0 -0
  71. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/registration/__init__.py +0 -0
  72. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/registration/controllers.py +0 -0
  73. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/registration/schemas.py +0 -0
  74. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/schemas.py +0 -0
  75. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/sessions/__init__.py +0 -0
  76. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/sessions/controllers.py +0 -0
  77. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/sessions/schemas.py +0 -0
  78. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/signals.py +0 -0
  79. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/templatetags/__init__.py +0 -0
  80. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/templatetags/spa_urls.py +0 -0
  81. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/tokens/__init__.py +0 -0
  82. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/tokens/app_settings.py +0 -0
  83. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/tokens/models.py +0 -0
  84. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/tokens/schemas.py +0 -0
  85. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/tokens/tokens.py +0 -0
  86. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/tokens/utils.py +0 -0
  87. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/auth/utils.py +0 -0
  88. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/celery/__init__.py +0 -0
  89. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/celery/base.py +0 -0
  90. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/celery/settings.py +0 -0
  91. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/conf.py +0 -0
  92. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/constants.py +0 -0
  93. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/context/__init__.py +0 -0
  94. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/context/site_name_processor.py +0 -0
  95. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/__init__.py +0 -0
  96. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/admin.py +0 -0
  97. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/apps.py +0 -0
  98. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/caches.py +0 -0
  99. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/controllers.py +0 -0
  100. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/enums.py +0 -0
  101. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/management/__init__.py +0 -0
  102. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/management/commands/__init__.py +0 -0
  103. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/management/commands/sync_currency.py +0 -0
  104. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/migrations/0001_initial.py +0 -0
  105. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/migrations/__init__.py +0 -0
  106. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/models.py +0 -0
  107. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/schemas.py +0 -0
  108. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/signals.py +0 -0
  109. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/tests.py +0 -0
  110. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/currency/utils.py +0 -0
  111. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/enums/__init__.py +0 -0
  112. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/enums/audit.py +0 -0
  113. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/enums/invoices.py +0 -0
  114. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/exceptions.py +0 -0
  115. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/functions.py +0 -0
  116. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/jwt/__init__.py +0 -0
  117. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/jwt/auth.py +0 -0
  118. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/jwt/middleware.py +0 -0
  119. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/jwt/utils.py +0 -0
  120. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/logger/__init__.py +0 -0
  121. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/logger/receivers.py +0 -0
  122. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/logger/settings.py +0 -0
  123. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/mixins/__init__.py +0 -0
  124. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/mixins/base.py +0 -0
  125. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/mixins/schemas.py +0 -0
  126. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/mixins/services.py +0 -0
  127. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/models/__init__.py +0 -0
  128. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/models/base.py +0 -0
  129. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/models/billing.py +0 -0
  130. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/models/change_tracker.py +0 -0
  131. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/models/fields.py +0 -0
  132. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/models/invoice.py +0 -0
  133. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/__init__.py +0 -0
  134. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/admin.py +0 -0
  135. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/apps.py +0 -0
  136. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/authorization.py +0 -0
  137. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/caches.py +0 -0
  138. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/checks.py +0 -0
  139. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/constants.py +0 -0
  140. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/context.py +0 -0
  141. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/controllers.py +0 -0
  142. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/enums.py +0 -0
  143. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/exceptions.py +0 -0
  144. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/locale/fr/LC_MESSAGES/django.mo +0 -0
  145. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/locale/fr/LC_MESSAGES/django.po +0 -0
  146. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/management/__init__.py +0 -0
  147. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/management/commands/__init__.py +0 -0
  148. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/management/commands/grant_tenant_owners.py +0 -0
  149. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/management/commands/init_oxiliere_system.py +0 -0
  150. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/migrations/__init__.py +0 -0
  151. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/models.py +0 -0
  152. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/schemas.py +0 -0
  153. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/services.py +0 -0
  154. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/settings.py +0 -0
  155. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/signals.py +0 -0
  156. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/tests.py +0 -0
  157. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/oxiliere/utils.py +0 -0
  158. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/pagination/__init__.py +0 -0
  159. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/pagination/cursor.py +0 -0
  160. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/pdf/__init__.py +0 -0
  161. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/pdf/printer.py +0 -0
  162. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/pdf/utils.py +0 -0
  163. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/pdf/views.py +0 -0
  164. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/__init__.py +0 -0
  165. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/actions.py +0 -0
  166. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/admin.py +0 -0
  167. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/apps.py +0 -0
  168. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/caches.py +0 -0
  169. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/checks.py +0 -0
  170. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/constants.py +0 -0
  171. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/controllers.py +0 -0
  172. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/exceptions.py +0 -0
  173. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/locale/fr/LC_MESSAGES/django.mo +0 -0
  174. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/locale/fr/LC_MESSAGES/django.po +0 -0
  175. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/management/__init__.py +0 -0
  176. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/management/commands/__init__.py +0 -0
  177. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/management/commands/load_permission_preset.py +0 -0
  178. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/0001_initial.py +0 -0
  179. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/0002_alter_grant_role.py +0 -0
  180. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +0 -0
  181. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/0004_group_app_group_permissions_app_9a9924_idx.py +0 -0
  182. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/0005_role_app.py +0 -0
  183. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/0006_alter_rolegrant_options_and_more.py +0 -0
  184. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/0007_grant_locked_alter_grant_role_and_more.py +0 -0
  185. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/0008_remove_grant_unique_user_scope_role_and_more.py +0 -0
  186. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/migrations/__init__.py +0 -0
  187. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/models.py +0 -0
  188. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/perms.py +0 -0
  189. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/queryset.py +0 -0
  190. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/schemas.py +0 -0
  191. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/services.py +0 -0
  192. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/tests.py +0 -0
  193. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/permissions/utils.py +0 -0
  194. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/py.typed +0 -0
  195. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/s3.py +0 -0
  196. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/templates/account/email/base_message.txt +0 -0
  197. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/templates/email/password/reset_email_message.html +0 -0
  198. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/templates/email/password/reset_email_message.txt +0 -0
  199. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/templates/email/password/reset_email_subject.txt +0 -0
  200. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/templates/email/signup/email_message.html +0 -0
  201. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/templates/email/signup/email_subject.txt +0 -0
  202. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/templates/verified.html +0 -0
  203. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/types.py +0 -0
  204. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/__init__.py +0 -0
  205. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/admin.py +0 -0
  206. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/apps.py +0 -0
  207. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/locale/fr/LC_MESSAGES/django.mo +0 -0
  208. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/locale/fr/LC_MESSAGES/django.po +0 -0
  209. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/migrations/0001_initial.py +0 -0
  210. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +0 -0
  211. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/migrations/0003_user_photo.py +0 -0
  212. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/migrations/0004_alter_user_options.py +0 -0
  213. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/migrations/__init__.py +0 -0
  214. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/models.py +0 -0
  215. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/tests.py +0 -0
  216. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/users/utils.py +0 -0
  217. {oxutils-0.3.2 → oxutils-0.4.0}/src/oxutils/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxutils
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Production-ready utilities for Django applications in the Oxiliere ecosystem
5
5
  Keywords: django,utilities,jwt,audit,logging,celery,structlog
6
6
  Author: Edimedia Mutoke
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oxutils"
3
- version = "0.3.2"
3
+ version = "0.4.0"
4
4
  description = "Production-ready utilities for Django applications in the Oxiliere ecosystem"
5
5
  readme = "README.md"
6
6
  license = "LGPL-3.0-only"
@@ -15,7 +15,7 @@ This package provides:
15
15
  - Custom exceptions, enums, and type definitions
16
16
  """
17
17
 
18
- __version__ = "0.3.1"
18
+ __version__ = "0.4.0"
19
19
 
20
20
  from oxutils.conf import AUDIT_MIDDLEWARE, UTILS_APPS
21
21
  from oxutils.settings import oxi_settings
@@ -1,10 +1,11 @@
1
1
  """
2
2
  Invitation backend – service layer for invitation lifecycle management.
3
3
  """
4
- import structlog
4
+
5
5
  from datetime import timedelta
6
6
  from typing import Optional
7
7
 
8
+ import structlog
8
9
  from django.conf import settings
9
10
  from django.db import transaction
10
11
  from django.db.models import QuerySet
@@ -13,15 +14,15 @@ from django.utils.translation import gettext_lazy as _
13
14
 
14
15
  from oxutils.auth.invitations.models import (
15
16
  BaseInvitation,
16
- InvitationStatus,
17
17
  InvitationRole,
18
+ InvitationStatus,
18
19
  get_invitation_model,
19
20
  )
20
21
  from oxutils.auth.invitations.tokens import InvitationTokenGenerator
21
22
  from oxutils.auth.signals import (
22
- invitation_sent,
23
23
  invitation_accepted,
24
24
  invitation_rejected,
25
+ invitation_sent,
25
26
  )
26
27
 
27
28
  logger = structlog.get_logger(__name__)
@@ -103,9 +104,7 @@ class InvitationBackend:
103
104
  user_id=str(user.pk),
104
105
  tenant=getattr(invitation.tenant, "oxi_id", str(invitation.tenant)),
105
106
  )
106
- invitation_accepted.send_robust(
107
- sender=self.__class__, invitation=invitation, user=user
108
- )
107
+ invitation_accepted.send_robust(sender=self.__class__, invitation=invitation, user=user)
109
108
  return invitation
110
109
 
111
110
  def cancel_invitation(self, token: str, cancelled_by):
@@ -134,9 +133,7 @@ class InvitationBackend:
134
133
  from allauth.account.models import EmailAddress
135
134
 
136
135
  Invitation = get_invitation_model()
137
- emails = list(
138
- EmailAddress.objects.filter(user=user).values_list("email", flat=True)
139
- )
136
+ emails = list(EmailAddress.objects.filter(user_id=user.pk).values_list("email", flat=True))
140
137
  if user.email:
141
138
  emails.append(user.email)
142
139
 
@@ -149,7 +146,9 @@ class InvitationBackend:
149
146
  def get_tenant_invitations(self, tenant) -> QuerySet:
150
147
  """Return all invitations for a tenant."""
151
148
  Invitation = get_invitation_model()
152
- return Invitation.objects.filter(tenant=tenant).select_related("invited_by", "invitee")
149
+ return Invitation.objects.filter(tenant_id=tenant.pk).select_related(
150
+ "invited_by", "invitee"
151
+ )
153
152
 
154
153
  def expire_stale_invitations(self) -> int:
155
154
  """Mark all expired pending invitations as EXPIRED. Returns count."""
@@ -185,9 +184,7 @@ class InvitationBackend:
185
184
 
186
185
  def _get_pending_invitation(self, token: str):
187
186
  Invitation = get_invitation_model()
188
- invitation = Invitation.objects.filter(
189
- token=token, status=InvitationStatus.PENDING
190
- ).first()
187
+ invitation = Invitation.objects.filter(token=token, status=InvitationStatus.PENDING).first()
191
188
  if invitation is None:
192
189
  raise ValueError(_("No pending invitation found with this token."))
193
190
  return invitation
@@ -1,23 +1,25 @@
1
1
  """
2
2
  Controllers for the invitations module.
3
3
  """
4
- from typing import List
5
4
 
6
5
  from django.http import HttpRequest
7
6
  from django.utils.translation import gettext_lazy as _
8
7
  from ninja_extra import (
9
8
  ControllerBase,
10
9
  api_controller,
11
- http_delete,
12
10
  http_get,
13
11
  http_post,
14
12
  )
13
+ from ninja_extra.pagination import (
14
+ PageNumberPaginationExtra,
15
+ PaginatedResponseSchema,
16
+ paginate,
17
+ )
15
18
  from ninja_extra.permissions import IsAuthenticated
16
19
  from ninja_extra.throttling import AnonRateThrottle, UserRateThrottle
17
20
 
18
21
  from oxutils.auth.invitations.backend import invitation_backend
19
22
  from oxutils.auth.invitations.models import InvitationStatus, get_invitation_model
20
- from oxutils.auth.signals import invitation_resent
21
23
  from oxutils.auth.invitations.schemas import (
22
24
  AcceptInvitationSchema,
23
25
  CancelInvitationSchema,
@@ -28,6 +30,7 @@ from oxutils.auth.invitations.schemas import (
28
30
  ResendInvitationSchema,
29
31
  ValidateTokenSchema,
30
32
  )
33
+ from oxutils.auth.signals import invitation_resent
31
34
  from oxutils.auth.utils import load_user
32
35
  from oxutils.exceptions import ExceptionCode
33
36
  from oxutils.mixins.schemas import ResponseSchema
@@ -124,31 +127,31 @@ class InvitationController(ControllerBase):
124
127
 
125
128
  # ── List user invitations ──────────────────────────────────────
126
129
 
127
- @http_get("", response=InvitationListSchema)
130
+ @http_get("", response=PaginatedResponseSchema[InvitationOutSchema])
131
+ @paginate(PageNumberPaginationExtra, page_size=20)
128
132
  @load_user
129
133
  def list_user_invitations(self, request: HttpRequest):
130
- """List all pending invitations for the current user."""
131
- qs = invitation_backend.get_user_invitations(request.user)
132
- invitations = [InvitationOutSchema.from_orm(inv) for inv in qs]
133
- return InvitationListSchema(invitations=invitations, total=len(invitations))
134
+ """List pending invitations for the current user (paginated)."""
135
+ return invitation_backend.get_user_invitations(request.user)
134
136
 
135
137
  # ── List tenant invitations ────────────────────────────────────
136
138
 
137
- @http_get("/tenant", response=InvitationListSchema)
139
+ @http_get("/tenant", response=PaginatedResponseSchema[InvitationOutSchema])
140
+ @paginate(PageNumberPaginationExtra, page_size=20)
138
141
  @load_user
139
142
  def list_tenant_invitations(self, request: HttpRequest):
140
- """List all invitations for the current tenant."""
143
+ """List invitations for the current tenant (paginated)."""
141
144
  tenant = getattr(request, "tenant", None)
142
145
  if tenant is None:
143
- return InvitationListSchema(invitations=[], total=0)
146
+ return get_invitation_model().objects.none()
144
147
 
145
- qs = invitation_backend.get_tenant_invitations(tenant)
146
- invitations = [InvitationOutSchema.from_orm(inv) for inv in qs]
147
- return InvitationListSchema(invitations=invitations, total=len(invitations))
148
+ return invitation_backend.get_tenant_invitations(tenant)
148
149
 
149
150
  # ── Validate token ─────────────────────────────────────────────
150
151
 
151
- @http_get("/validate/{token}", response=ValidateTokenSchema, auth=None, throttle=[AnonRateThrottle()])
152
+ @http_get(
153
+ "/validate/{token}", response=ValidateTokenSchema, auth=None, throttle=[AnonRateThrottle()]
154
+ )
152
155
  def validate_token(self, request: HttpRequest, token: str):
153
156
  """Validate an invitation token (public endpoint – no auth required)."""
154
157
  invitation = invitation_backend.validate_token(token)
@@ -235,9 +238,9 @@ class InvitationController(ControllerBase):
235
238
  }
236
239
 
237
240
  inviter_level = role_hierarchy.get(
238
- InvitationRole.OWNER if membership.is_owner else (
239
- InvitationRole.ADMIN if membership.is_admin else InvitationRole.MEMBER
240
- ),
241
+ InvitationRole.OWNER
242
+ if membership.is_owner
243
+ else (InvitationRole.ADMIN if membership.is_admin else InvitationRole.MEMBER),
241
244
  0,
242
245
  )
243
246
  requested_level = role_hierarchy.get(requested_role, 0)
@@ -1,22 +1,21 @@
1
1
  """
2
2
  Schemas for the invitations module.
3
3
  """
4
- from datetime import datetime
4
+
5
5
  from typing import List, Optional
6
- from uuid import UUID
7
6
 
7
+ from django.utils.translation import gettext_lazy as _
8
8
  from ninja import ModelSchema, Schema
9
9
  from pydantic import EmailStr, field_validator
10
- from django.utils.translation import gettext_lazy as _
11
-
12
- from oxutils.auth.invitations.models import BaseInvitation, InvitationRole, InvitationStatus
13
10
 
11
+ from oxutils.auth.invitations.models import BaseInvitation, InvitationRole
14
12
 
15
13
  # ── Request schemas ────────────────────────────────────────────────
16
14
 
15
+
17
16
  class CreateInvitationSchema(Schema):
18
17
  email: EmailStr
19
- role: str = InvitationRole.MEMBER
18
+ role: InvitationRole = InvitationRole.MEMBER
20
19
  message: str = ""
21
20
 
22
21
  @field_validator("email", mode="before")
@@ -28,9 +27,9 @@ class CreateInvitationSchema(Schema):
28
27
  @classmethod
29
28
  def validate_role(cls, v: str) -> str:
30
29
  if v not in InvitationRole.values:
31
- raise ValueError(_("Invalid role. Must be one of: {}").format(
32
- ", ".join(InvitationRole.values)
33
- ))
30
+ raise ValueError(
31
+ _("Invalid role. Must be one of: {}").format(", ".join(InvitationRole.values))
32
+ )
34
33
  return v
35
34
 
36
35
 
@@ -48,6 +47,7 @@ class ResendInvitationSchema(Schema):
48
47
 
49
48
  # ── Response schemas ───────────────────────────────────────────────
50
49
 
50
+
51
51
  class InvitationOutSchema(ModelSchema):
52
52
  tenant_name: Optional[str] = None
53
53
  invited_by_email: Optional[str] = None
@@ -56,8 +56,15 @@ class InvitationOutSchema(ModelSchema):
56
56
  class Meta:
57
57
  model = BaseInvitation
58
58
  fields = [
59
- "id", "email", "token", "status", "role",
60
- "expires_at", "accepted_at", "created_at", "message",
59
+ "id",
60
+ "email",
61
+ "token",
62
+ "status",
63
+ "role",
64
+ "expires_at",
65
+ "accepted_at",
66
+ "created_at",
67
+ "message",
61
68
  ]
62
69
 
63
70
  @staticmethod
@@ -0,0 +1,24 @@
1
+ from uuid import UUID
2
+
3
+ from django.utils.functional import cached_property
4
+ from ninja_jwt.models import TokenUser as DefaultTonkenUser
5
+ from ninja_jwt.settings import api_settings
6
+
7
+
8
+ class TokenUser(DefaultTonkenUser):
9
+ @cached_property
10
+ def id(self):
11
+ return UUID(self.token[api_settings.USER_ID_CLAIM])
12
+
13
+ @property
14
+ def oxi_id(self):
15
+ # for compatibility with the User model
16
+ return self.id
17
+
18
+ @cached_property
19
+ def token_created_at(self):
20
+ return self.token.get("cat", None)
21
+
22
+ @cached_property
23
+ def token_session(self):
24
+ return self.token.get("session", None)
@@ -0,0 +1,3 @@
1
+ from ninja_jwt.tokens import AccessToken # noqa: F401 — re-exported for convenience
2
+
3
+ __all__ = ["AccessToken"]
@@ -0,0 +1,47 @@
1
+ from cacheops import cached
2
+ from django.db import connection
3
+
4
+ # ── Tenant token TTL ──────────────────────────────────────────────
5
+
6
+ TENANT_TOKEN_TTL = 3600 # 60 minutes
7
+
8
+
9
+ def cacheops_prefix(query):
10
+ if connection.schema_name:
11
+ return '%s:' % connection.schema_name
12
+ return 'default:'
13
+
14
+
15
+ # ── Tenant token cache (cacheops @cached pattern) ──────────────────
16
+
17
+ @cached(timeout=TENANT_TOKEN_TTL)
18
+ def get_cached_tenant_token(oxi_id: str, user_id: str):
19
+ """Return the tenant DB object for *oxi_id* + *user_id*, fetching on miss.
20
+
21
+ The returned tenant has ``.user`` pre-attached (the matching
22
+ *TenantUser* row). Cacheops caches the result automatically.
23
+ Raises ``ObjectDoesNotExist`` when the tenant or tenant-user
24
+ is not found, so the miss is **not** cached.
25
+ """
26
+ from django.core.exceptions import ObjectDoesNotExist
27
+ from django_tenants.utils import get_tenant_model
28
+
29
+ TenantModel = get_tenant_model()
30
+
31
+ try:
32
+ tenant = TenantModel.objects.get(oxi_id=oxi_id)
33
+ except TenantModel.DoesNotExist as ex:
34
+ raise ObjectDoesNotExist(f"tenant not found: {oxi_id}") from ex
35
+
36
+ try:
37
+ tenant_user = tenant.users.select_related("user").get(user__pk=user_id)
38
+ except (ObjectDoesNotExist, ValueError) as ex:
39
+ raise ObjectDoesNotExist(f"tenant_user not found: {oxi_id}/{user_id}") from ex
40
+
41
+ tenant.user = tenant_user
42
+ return tenant
43
+
44
+
45
+ def delete_cached_tenant_token(oxi_id: str, user_id: str) -> None:
46
+ """Remove the cached tenant (e.g. on tenant-switch or logout)."""
47
+ get_cached_tenant_token.invalidate(oxi_id, user_id)
@@ -14,13 +14,13 @@ from django_tenants.utils import (
14
14
  has_multi_type_tenants,
15
15
  )
16
16
 
17
- from oxutils.constants import ORGANIZATION_HEADER_KEY, ORGANIZATION_TOKEN_COOKIE_KEY
18
- from oxutils.jwt.models import TokenTenant
19
- from oxutils.jwt.tokens import OrganizationAccessToken
17
+ from oxutils.constants import ORGANIZATION_HEADER_KEY
18
+ from oxutils.oxiliere.cacheops import (
19
+ delete_cached_tenant_token,
20
+ get_cached_tenant_token,
21
+ )
20
22
  from oxutils.oxiliere.context import set_current_tenant_schema_name
21
23
  from oxutils.oxiliere.enums import TenantStatus
22
- from oxutils.oxiliere.utils import is_system_tenant
23
- from oxutils.settings import oxi_settings
24
24
 
25
25
  logger = structlog.get_logger(__name__)
26
26
 
@@ -69,112 +69,60 @@ class TenantMainMiddleware(MiddlewareMixin):
69
69
  connection.set_schema_to_public()
70
70
 
71
71
  oxi_id = self.get_org_id_from_request(request)
72
-
73
- # Try to get tenant from cookie token first
74
- tenant_token = request.COOKIES.get(ORGANIZATION_TOKEN_COOKIE_KEY)
75
72
  tenant = None
76
- old_tenant = None
77
- request._should_set_tenant_cookie = False
78
-
79
- if tenant_token:
80
- tenant = TokenTenant.for_token(tenant_token)
81
- # Verify the token's oxi_id matches the request
82
- if tenant and not is_system_tenant(tenant) and tenant.oxi_id != oxi_id:
83
- logger.info(
84
- "tenant_token_oxi_id_doesnt_match_request_oxi_id",
85
- tenant_oxi_id=tenant.oxi_id,
86
- request_oxi_id=oxi_id,
87
- )
88
- old_tenant = tenant
89
- tenant = None
90
-
91
- if (
92
- tenant
93
- and hasattr(request, "user")
94
- and request.user
95
- and tenant.user.oxi_id != request.user.id
96
- ):
97
- logger.info(
98
- "tenant_user_token_oxi_id_doesnt_match",
99
- tenant_oxi_id=tenant.oxi_id,
100
- user_oxi_id=request.user.id,
101
- )
102
- old_tenant = tenant
103
- tenant = None
104
-
105
- # If no valid token, fetch from database
106
- if not tenant:
107
- if oxi_id: # fetch with oxi_id on tenant
108
- try:
109
- tenant = self.get_tenant(oxi_id)
110
- tenant.user = self.get_tenant_user(tenant, request.user, raise_exception=True)
111
-
112
- # Mark that we need to set the cookie in the response
113
- request._should_set_tenant_cookie = True
114
-
115
- if old_tenant:
116
- logger.info(
117
- "tenant_changed", old_tenant=old_tenant.oxi_id, new_tenant=tenant.oxi_id
118
- )
119
-
120
- except ObjectDoesNotExist as ex:
121
- logger.error("tenant_not_found", oxi_id=oxi_id, error=str(ex))
122
- default_tenant = self.no_tenant_found(request, oxi_id)
123
- return default_tenant
124
- else: # try to return the system tenant
125
- try:
126
- from oxutils.oxiliere.caches import get_system_tenant
127
-
128
- tenant = get_system_tenant()
129
-
130
- if hasattr(request, "user") and request.user:
131
- tenant.user = self.get_tenant_user(
132
- tenant, request.user, raise_exception=False
133
- )
134
- else:
135
- tenant.user = None
136
-
137
- request._should_set_tenant_cookie = True
138
- except Exception as e:
139
- logger.error("system_tenant_not_found", error=str(e))
140
- from django.http import HttpResponseBadRequest
141
-
142
- return HttpResponseBadRequest("Missing X-Organization-ID header")
143
73
 
74
+ # ── Normal path: org + user → cache (or DB on miss) ──────────
75
+ if oxi_id and hasattr(request, "user") and request.user and request.user.is_authenticated:
76
+ user_id = str(request.user.id)
77
+ try:
78
+ tenant = get_cached_tenant_token(oxi_id, user_id)
79
+ except ObjectDoesNotExist:
80
+ logger.info("tenant_not_found", oxi_id=oxi_id, user_id=user_id)
81
+ return self.no_tenant_found(request, oxi_id)
82
+
83
+ elif not oxi_id:
84
+ # ── No org header → system tenant ────────────────────────
85
+ try:
86
+ from oxutils.oxiliere.caches import get_system_tenant
87
+
88
+ tenant = get_system_tenant()
89
+
90
+ if hasattr(request, "user") and request.user:
91
+ tenant.user = self.get_tenant_user(
92
+ tenant, request.user, raise_exception=False
93
+ )
94
+ else:
95
+ tenant.user = None
96
+ except Exception as e:
97
+ logger.error("system_tenant_not_found", error=str(e))
98
+ from django.http import HttpResponseBadRequest
99
+
100
+ return HttpResponseBadRequest("Missing X-Organization-ID header")
101
+ else:
102
+ # oxi_id present but no authenticated user
103
+ return self.no_tenant_found(request, oxi_id)
104
+
105
+ # ── Status guards ─────────────────────────────────────────────
144
106
  if tenant.is_deleted or not tenant.is_active:
145
107
  logger.error("tenant_is_deleted_or_inactive", oxi_id=oxi_id)
108
+ if oxi_id and hasattr(request, "user") and request.user and request.user.is_authenticated:
109
+ delete_cached_tenant_token(oxi_id, str(request.user.id))
146
110
  return self.no_tenant_found(request, oxi_id)
147
111
 
148
- # Delegate status-specific responses
149
112
  status_response = self._handle_tenant_status(request, tenant)
150
113
  if status_response is not None:
151
114
  return status_response
152
115
 
153
- if tenant and isinstance(tenant, TokenTenant):
154
- request.db_tenant = None
155
- request.tenant = tenant
156
- else:
157
- request.db_tenant = tenant
158
- request.tenant = TokenTenant.from_db(tenant)
116
+ # ── Attach tenant to request ──────────────────────────────────
117
+ request.db_tenant = None
118
+ request.tenant = tenant
159
119
 
160
120
  set_current_tenant_schema_name(tenant.schema_name)
161
121
  connection.set_tenant(request.tenant)
162
122
  self.setup_url_routing(request)
163
123
 
164
124
  def process_response(self, request, response):
165
- """Set the tenant token cookie if needed."""
166
- if hasattr(request, "_should_set_tenant_cookie") and request._should_set_tenant_cookie:
167
- if hasattr(request, "db_tenant") and isinstance(request.db_tenant, self.tenant_model):
168
- # Generate token from DB tenant
169
- token = OrganizationAccessToken.for_tenant(request.db_tenant)
170
- response.set_cookie(
171
- key=ORGANIZATION_TOKEN_COOKIE_KEY,
172
- value=str(token),
173
- max_age=60 * oxi_settings.jwt_org_access_token_lifetime,
174
- httponly=True,
175
- secure=getattr(settings, "SESSION_COOKIE_SECURE", False),
176
- samesite="Lax",
177
- )
125
+ """Cache is handled by :func:`get_cached_tenant_token` nothing to do."""
178
126
  return response
179
127
 
180
128
  def no_tenant_found(self, request, oxi_id):
@@ -0,0 +1,97 @@
1
+ import structlog
2
+ from ninja_extra.permissions import BasePermission
3
+
4
+ logger = structlog.get_logger(__name__)
5
+
6
+
7
+ class TenantBasePermission(BasePermission):
8
+ """
9
+ Vérifie que l'utilisateur a accès au tenant actuel.
10
+ L'utilisateur doit être authentifié et avoir un lien avec le tenant.
11
+ """
12
+
13
+ def check_tenant_permission(self, request) -> bool:
14
+ raise NotImplementedError("Subclasses must implement this method")
15
+
16
+ def has_permission(self, request, controller=None, **kwargs):
17
+ if not request.user or not request.user.is_authenticated:
18
+ return False
19
+
20
+ if not hasattr(request, "tenant") or not request.tenant:
21
+ logger.warning("tenant_permission", type="tenant_not_found")
22
+ return False
23
+
24
+ if not hasattr(request.tenant, "user") or not request.tenant.user:
25
+ logger.warning(
26
+ "tenant_permission",
27
+ type="tenant_user_not_attached",
28
+ tenant=getattr(request.tenant, "oxi_id", None),
29
+ )
30
+ return False
31
+
32
+ return self.check_tenant_permission(request)
33
+
34
+
35
+ class TenantUserPermission(TenantBasePermission):
36
+ """
37
+ Vérifie que l'utilisateur est un membre du tenant actuel.
38
+ Alias de TenantPermission pour plus de clarté sémantique.
39
+ """
40
+
41
+ def check_tenant_permission(self, request) -> bool:
42
+ tenant = request.tenant
43
+ passed = getattr(tenant.user, "status", None) == "active"
44
+
45
+ logger.info(
46
+ "tenant_permission",
47
+ type="tenant_user_access_permission",
48
+ tenant=tenant.oxi_id,
49
+ passed=passed,
50
+ )
51
+
52
+ return passed
53
+
54
+
55
+ class TenantOwnerPermission(TenantBasePermission):
56
+ """
57
+ Vérifie que l'utilisateur est propriétaire (owner) du tenant actuel.
58
+ """
59
+
60
+ def check_tenant_permission(self, request) -> bool:
61
+ tenant = request.tenant
62
+ active = getattr(tenant.user, "status", None) == "active"
63
+ passed = active and getattr(tenant.user, "is_owner", False)
64
+
65
+ logger.info(
66
+ "tenant_permission",
67
+ type="tenant_user_access_permission",
68
+ tenant=tenant.oxi_id,
69
+ passed=passed,
70
+ )
71
+
72
+ return passed
73
+
74
+
75
+ class TenantAdminPermission(TenantBasePermission):
76
+ """
77
+ Vérifie que l'utilisateur est admin ou owner du tenant actuel.
78
+ """
79
+
80
+ def check_tenant_permission(self, request) -> bool:
81
+ tenant = request.tenant
82
+ active = getattr(tenant.user, "status", None) == "active"
83
+ passed = active and getattr(tenant.user, "is_admin", False)
84
+
85
+ logger.info(
86
+ "tenant_permission",
87
+ type="tenant_user_access_permission",
88
+ tenant=tenant.oxi_id,
89
+ passed=passed,
90
+ )
91
+
92
+ return passed
93
+
94
+
95
+ IsTenantUser = TenantUserPermission()
96
+ IsTenantOwner = TenantOwnerPermission()
97
+ IsTenantAdmin = TenantAdminPermission()
@@ -29,12 +29,8 @@ class OxUtilsSettings(BaseSettings):
29
29
  jwt_verifying_key: Optional[str] = None
30
30
  jwt_jwks_url: Optional[str] = None
31
31
  jwt_access_token_key: str = Field('access')
32
- jwt_org_access_token_key: str = Field('org_access')
33
- jwt_service_token_key: str = Field('service')
34
32
  jwt_algorithm: Optional[str] = Field('RS256')
35
33
  jwt_access_token_lifetime: int = Field(15) # minutes
36
- jwt_service_token_lifetime: int = Field(3) # minutes
37
- jwt_org_access_token_lifetime: int = Field(60) # minutes
38
34
 
39
35
 
40
36
  # AuditLog