modoboa 2.4.11__py3-none-any.whl → 2.5.0__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 (314) hide show
  1. modoboa/admin/api/v2/serializers.py +14 -0
  2. modoboa/amavis/__init__.py +3 -0
  3. modoboa/amavis/app_settings.py +276 -0
  4. modoboa/amavis/apps.py +18 -0
  5. modoboa/amavis/checks/__init__.py +2 -0
  6. modoboa/amavis/checks/settings_checks.py +59 -0
  7. modoboa/amavis/dbrouter.py +35 -0
  8. modoboa/amavis/factories.py +164 -0
  9. modoboa/amavis/handlers.py +146 -0
  10. modoboa/amavis/lib.py +381 -0
  11. modoboa/amavis/management/__init__.py +0 -0
  12. modoboa/amavis/management/commands/__init__.py +0 -0
  13. modoboa/amavis/management/commands/amnotify.py +99 -0
  14. modoboa/amavis/management/commands/qcleanup.py +84 -0
  15. modoboa/amavis/migrations/0001_initial.py +340 -0
  16. modoboa/amavis/migrations/__init__.py +0 -0
  17. modoboa/amavis/models.py +226 -0
  18. modoboa/amavis/serializers.py +139 -0
  19. modoboa/amavis/sql_connector.py +240 -0
  20. modoboa/amavis/sql_email.py +66 -0
  21. modoboa/amavis/tasks.py +33 -0
  22. modoboa/amavis/templates/amavis/notifications/pending_requests.html +16 -0
  23. modoboa/amavis/tests/__init__.py +0 -0
  24. modoboa/amavis/tests/sa-learn +3 -0
  25. modoboa/amavis/tests/sample_messages/quarantined-input.txt +80 -0
  26. modoboa/amavis/tests/sample_messages/quarantined-output-plain_nolinks.txt +17 -0
  27. modoboa/amavis/tests/spamc +3 -0
  28. modoboa/amavis/tests/test_checks.py +25 -0
  29. modoboa/amavis/tests/test_handlers.py +214 -0
  30. modoboa/amavis/tests/test_lib.py +90 -0
  31. modoboa/amavis/tests/test_management_commands.py +45 -0
  32. modoboa/amavis/tests/test_sql_email.py +67 -0
  33. modoboa/amavis/tests/test_utils.py +19 -0
  34. modoboa/amavis/tests/test_viewsets.py +319 -0
  35. modoboa/amavis/urls.py +11 -0
  36. modoboa/amavis/utils.py +105 -0
  37. modoboa/amavis/viewsets.py +265 -0
  38. modoboa/core/api/v1/serializers.py +7 -5
  39. modoboa/core/api/v2/serializers.py +4 -2
  40. modoboa/core/api/v2/tests.py +16 -4
  41. modoboa/core/api/v2/urls.py +5 -5
  42. modoboa/core/api/v2/views.py +3 -1
  43. modoboa/core/api/v2/viewsets.py +24 -3
  44. modoboa/core/handlers.py +6 -2
  45. modoboa/core/management/commands/add_allowed_hosts.py +33 -0
  46. modoboa/core/management/commands/load_initial_data.py +10 -0
  47. modoboa/core/migrations/0025_rename_user_email_is_active_core_user_email_c0c03f_idx.py +23 -5
  48. modoboa/core/tests/test_core.py +24 -0
  49. modoboa/core/utils.py +3 -0
  50. modoboa/frontend_dist/assets/AccountAliasForm-BuSy_1n9.js +1 -0
  51. modoboa/frontend_dist/assets/AccountEditView-qdJmLM_e.js +1 -0
  52. modoboa/frontend_dist/assets/AccountLayout-DrN7vHsX.js +1 -0
  53. modoboa/frontend_dist/assets/AccountPasswordSubForm-DZGt_Xgq.js +1 -0
  54. modoboa/frontend_dist/assets/AccountView-CO65y0vZ.js +1 -0
  55. modoboa/frontend_dist/assets/AddressBook-BZNUlhek.js +1 -0
  56. modoboa/frontend_dist/assets/AdminLayout-CTNhuwTw.js +1 -0
  57. modoboa/frontend_dist/assets/AlarmsView-9yKGbmkC.css +1 -0
  58. modoboa/frontend_dist/assets/AlarmsView-DN_JIw9g.js +1 -0
  59. modoboa/frontend_dist/assets/AliasEditView-DjpPUTp9.js +1 -0
  60. modoboa/frontend_dist/assets/{AliasRecipientForm-IOae6sjF.js → AliasRecipientForm-B1Y8wFdP.js} +1 -1
  61. modoboa/frontend_dist/assets/AliasView-GOJ5lyQH.js +1 -0
  62. modoboa/frontend_dist/assets/AuditTrailView-fbXmq70e.js +1 -0
  63. modoboa/frontend_dist/assets/CalendarView-LlQQNEPL.js +1 -0
  64. modoboa/frontend_dist/assets/{ChoiceField-DJ_c78Cm.js → ChoiceField-B3ReQHVe.js} +1 -1
  65. modoboa/frontend_dist/assets/ComposeEmailForm-Bs1fZXAL.js +1 -0
  66. modoboa/frontend_dist/assets/ComposeEmailView-s3LMl3pO.js +1 -0
  67. modoboa/frontend_dist/assets/ConfirmDialog-DY_kUHLG.js +1 -0
  68. modoboa/frontend_dist/assets/{ConnectedLayout-Dvwmicnc.css → ConnectedLayout-Bxh21hcH.css} +1 -1
  69. modoboa/frontend_dist/assets/ConnectedLayout-UWjiYBNw.js +1 -0
  70. modoboa/frontend_dist/assets/CreationForm-ORg3fazt.js +1 -0
  71. modoboa/frontend_dist/assets/DashboardView-Dplk9itS.js +1 -0
  72. modoboa/frontend_dist/assets/DomainAdminList-DVn9x0rB.js +1 -0
  73. modoboa/frontend_dist/assets/DomainEditView-nAoL64D_.js +1 -0
  74. modoboa/frontend_dist/assets/{DomainTransportForm-C2xo0Yd7.js → DomainTransportForm-CA-DNUxX.js} +1 -1
  75. modoboa/frontend_dist/assets/{DomainView-BDKoBFYr.css → DomainView-CCLYXPHx.css} +1 -1
  76. modoboa/frontend_dist/assets/DomainView-CdXPpwJG.js +5 -0
  77. modoboa/frontend_dist/assets/DomainsView-B_59gowf.js +1 -0
  78. modoboa/frontend_dist/assets/DomainsView-DZ-ss9bI.css +1 -0
  79. modoboa/frontend_dist/assets/EmailField-CwcwI5xW.js +1 -0
  80. modoboa/frontend_dist/assets/EmailView-BshxcfAK.js +1 -0
  81. modoboa/frontend_dist/assets/EmptyLayout-DFfhnhLi.js +1 -0
  82. modoboa/frontend_dist/assets/FiltersView-Cf20MSTK.js +1 -0
  83. modoboa/frontend_dist/assets/ForwardEmailView-CZG062os.js +1 -0
  84. modoboa/frontend_dist/assets/{HtmlEditor-CJ9umKeO.js → HtmlEditor-Bh4c689R.js} +1 -1
  85. modoboa/frontend_dist/assets/IdentitiesView-BXAuU1YX.js +1 -0
  86. modoboa/frontend_dist/assets/{IdentitiesView-0ziuQ5s-.css → IdentitiesView-DPrrRMS5.css} +1 -1
  87. modoboa/frontend_dist/assets/InformationView-C9vvvQhJ.css +1 -0
  88. modoboa/frontend_dist/assets/InformationView-Cn5FZW7H.js +1 -0
  89. modoboa/frontend_dist/assets/{LoadingData-CYwX3Jpn.js → LoadingData-CdVvm4FI.js} +1 -1
  90. modoboa/frontend_dist/assets/{LoginCallbackView-E01qkKn0.js → LoginCallbackView-B9hAH4MI.js} +1 -1
  91. modoboa/frontend_dist/assets/{LoginView-Cy4uFV9h.js → LoginView-tHIR4Adc.js} +1 -1
  92. modoboa/frontend_dist/assets/MailboxView-Bugu2vhg.js +1 -0
  93. modoboa/frontend_dist/assets/MenuItems-PXjiG-fs.js +1 -0
  94. modoboa/frontend_dist/assets/MessageView-Cy4STShm.js +1 -0
  95. modoboa/frontend_dist/assets/MessagesView-DdkuEgfX.js +1 -0
  96. modoboa/frontend_dist/assets/MigrationsView-CidSEjCF.js +1 -0
  97. modoboa/frontend_dist/assets/{ParametersForm-BZM0QSvg.js → ParametersForm-CAv4SH-E.js} +1 -1
  98. modoboa/frontend_dist/assets/ParametersView-CX7Ffemw.js +1 -0
  99. modoboa/frontend_dist/assets/ParametersView-CrbNcmV3.js +1 -0
  100. modoboa/frontend_dist/assets/ProviderEditView-CrltAQXl.js +1 -0
  101. modoboa/frontend_dist/assets/ProviderGeneralForm-BYAzVnXM.js +1 -0
  102. modoboa/frontend_dist/assets/ProvidersView-osjIY4Ex.js +1 -0
  103. modoboa/frontend_dist/assets/QuarantineLayout-B8EcU9vS.js +1 -0
  104. modoboa/frontend_dist/assets/QuarantineView-D4gOE4EQ.css +1 -0
  105. modoboa/frontend_dist/assets/QuarantineView-D8Qg0MXA.js +1 -0
  106. modoboa/frontend_dist/assets/ReplyEmailView-BABPqWhd.js +1 -0
  107. modoboa/frontend_dist/assets/ResourcesForm-OaqdRYVs.js +1 -0
  108. modoboa/frontend_dist/assets/SelfServiceLayout-d277YTGR.js +1 -0
  109. modoboa/frontend_dist/assets/{SettingsView-BxLJBFY0.js → SettingsView-9iNcDhkI.js} +2 -2
  110. modoboa/frontend_dist/assets/StatisticsView-cHsPyGkL.js +1 -0
  111. modoboa/frontend_dist/assets/TimeSerieChart--V83dcJ9.js +1 -0
  112. modoboa/frontend_dist/assets/UserLayout-B3sBiTcZ.js +1 -0
  113. modoboa/frontend_dist/assets/VAlert-BuaaYN2h.js +1 -0
  114. modoboa/frontend_dist/assets/VApp-CKP-6zGP.js +1 -0
  115. modoboa/frontend_dist/assets/VAutocomplete-Dwv6_Rzq.js +1 -0
  116. modoboa/frontend_dist/assets/VAvatar-Cmga0vj6.js +1 -0
  117. modoboa/frontend_dist/assets/VBadge-BQrRJ9S0.css +1 -0
  118. modoboa/frontend_dist/assets/VBadge-CixeK87a.js +1 -0
  119. modoboa/frontend_dist/assets/VCard-CxH9DWoK.js +1 -0
  120. modoboa/frontend_dist/assets/VCheckbox-62GOpvvP.js +1 -0
  121. modoboa/frontend_dist/assets/{VCheckboxBtn-Dt810gWf.js → VCheckboxBtn-DMoNtKT8.js} +1 -1
  122. modoboa/frontend_dist/assets/VChip-D_styETR.js +1 -0
  123. modoboa/frontend_dist/assets/VColorPicker-BHscBGQV.js +1 -0
  124. modoboa/frontend_dist/assets/{VContainer-DvTbsotR.js → VContainer-B46caNs1.js} +1 -1
  125. modoboa/frontend_dist/assets/VDataTable-Bh8NbVSx.js +1 -0
  126. modoboa/frontend_dist/assets/VDataTableServer-BDR5hOmo.js +1 -0
  127. modoboa/frontend_dist/assets/VDataTableVirtual-BOQlNtIG.js +1 -0
  128. modoboa/frontend_dist/assets/{VDialog-Bk6EWNhz.js → VDialog-BcTg7w6P.js} +1 -1
  129. modoboa/frontend_dist/assets/VExpansionPanels-BmH5Jl2Z.js +1 -0
  130. modoboa/frontend_dist/assets/VFileInput-BC4yAygd.js +1 -0
  131. modoboa/frontend_dist/assets/VForm-D5iPGkde.js +1 -0
  132. modoboa/frontend_dist/assets/VInput-CcxkaOXT.css +1 -0
  133. modoboa/frontend_dist/assets/VInput-CoDJzvaW.js +1 -0
  134. modoboa/frontend_dist/assets/VMenu-gUG70-zD.js +1 -0
  135. modoboa/frontend_dist/assets/VPicker-BXuKT3zB.js +1 -0
  136. modoboa/frontend_dist/assets/VProgressCircular-BtOPiGCg.js +1 -0
  137. modoboa/frontend_dist/assets/VRadioGroup-DIFZKSn-.js +1 -0
  138. modoboa/frontend_dist/assets/{VRow-BF35mT1S.js → VRow-ozg66L7j.js} +1 -1
  139. modoboa/frontend_dist/assets/VSelect-C3RjAa45.js +1 -0
  140. modoboa/frontend_dist/assets/VSelectionControl-zyz-fJvC.js +1 -0
  141. modoboa/frontend_dist/assets/VSheet-BNx2X4Mk.js +1 -0
  142. modoboa/frontend_dist/assets/VSpacer-DinPiXs9.js +1 -0
  143. modoboa/frontend_dist/assets/VSwitch-DwxdeAEq.js +1 -0
  144. modoboa/frontend_dist/assets/VTable-DaLxa4FO.js +1 -0
  145. modoboa/frontend_dist/assets/VTabs-BP0Hgsgm.js +1 -0
  146. modoboa/frontend_dist/assets/VTextField-BzBVKKob.css +1 -0
  147. modoboa/frontend_dist/assets/VTextField-XoGTj1KG.js +1 -0
  148. modoboa/frontend_dist/assets/VTextarea-wBlRMIv_.js +1 -0
  149. modoboa/frontend_dist/assets/VToolbar-CFZfqeOr.js +1 -0
  150. modoboa/frontend_dist/assets/VWindowItem-BB7ETW3b.js +1 -0
  151. modoboa/frontend_dist/assets/WebmailLayout-_Hk1XhVq.js +1 -0
  152. modoboa/frontend_dist/assets/accounts-DUzbx6k8.js +1 -0
  153. modoboa/frontend_dist/assets/{admin-o-HRGnmT.js → admin-DewTk2H8.js} +1 -1
  154. modoboa/frontend_dist/assets/{aliases-DDVeehyg.js → aliases-4sXmjwXp.js} +1 -1
  155. modoboa/frontend_dist/assets/amavis-CC0li7_T.js +1 -0
  156. modoboa/frontend_dist/assets/amavis-DK8SHE6o.js +1 -0
  157. modoboa/frontend_dist/assets/{contacts-C84DY-Q1.js → contacts-BjghrPqZ.js} +1 -1
  158. modoboa/frontend_dist/assets/{domains-Bgn4ixHL.js → domains-BSawReeu.js} +1 -1
  159. modoboa/frontend_dist/assets/{domains.store-DTE-V7Y1.js → domains.store-D-vWCEIK.js} +1 -1
  160. modoboa/frontend_dist/assets/{filter-CnffiQAW.js → filter-C82FUCw_.js} +1 -1
  161. modoboa/frontend_dist/assets/forwardRefs-cvcnlhoK.js +1 -0
  162. modoboa/frontend_dist/assets/global.store-DbkcI5o2.js +1 -0
  163. modoboa/frontend_dist/assets/{importExport-BlQYb0NO.js → importExport-DzoL4Mvc.js} +1 -1
  164. modoboa/frontend_dist/assets/index-BImkz5Jx.js +984 -0
  165. modoboa/frontend_dist/assets/layout-C5FyYCHK.js +1 -0
  166. modoboa/frontend_dist/assets/{layout.store-DkjrAoXt.js → layout.store-NXWtFIwL.js} +1 -1
  167. modoboa/frontend_dist/assets/{logos-q8SEyAa4.js → logos-BswdveCV.js} +1 -1
  168. modoboa/frontend_dist/assets/{logs-B7IJ7LBa.js → logs-6CbtfaZS.js} +1 -1
  169. modoboa/frontend_dist/assets/{parameters-A6iBEYQq.js → parameters-aSQiR7kN.js} +1 -1
  170. modoboa/frontend_dist/assets/{parameters.store-BiXS4_6w.js → parameters.store-CzQqVatx.js} +1 -1
  171. modoboa/frontend_dist/assets/permissions-DNoefz-n.js +1 -0
  172. modoboa/frontend_dist/assets/{ssrBoot-AzTdjPjk.js → ssrBoot-CKUX4kcb.js} +1 -1
  173. modoboa/frontend_dist/assets/{tag-BnSYRTcD.js → tag-B_yWNNJD.js} +1 -1
  174. modoboa/frontend_dist/assets/transports-BDNB9wR5.js +1 -0
  175. modoboa/frontend_dist/assets/{webmail-CSH_3l6R.js → webmail-CdU6CD9b.js} +1 -1
  176. modoboa/frontend_dist/index.html +1 -1
  177. modoboa/lib/email_utils.py +2 -2
  178. modoboa/lib/permissions.py +7 -0
  179. modoboa/lib/test_runners.py +29 -0
  180. modoboa/lib/tests/__init__.py +5 -1
  181. modoboa/locale/br/LC_MESSAGES/django.po +87 -75
  182. modoboa/locale/cs/LC_MESSAGES/django.po +82 -74
  183. modoboa/locale/cs_CZ/LC_MESSAGES/django.po +145 -121
  184. modoboa/locale/de/LC_MESSAGES/django.mo +0 -0
  185. modoboa/locale/de/LC_MESSAGES/django.po +339 -651
  186. modoboa/locale/de_DE/LC_MESSAGES/django.po +87 -75
  187. modoboa/locale/el_GR/LC_MESSAGES/django.po +160 -135
  188. modoboa/locale/en/LC_MESSAGES/django.po +82 -74
  189. modoboa/locale/es/LC_MESSAGES/django.po +158 -131
  190. modoboa/locale/es_MX/LC_MESSAGES/django.po +82 -74
  191. modoboa/locale/fi/LC_MESSAGES/django.po +87 -75
  192. modoboa/locale/fr/LC_MESSAGES/django.mo +0 -0
  193. modoboa/locale/fr/LC_MESSAGES/django.po +469 -201
  194. modoboa/locale/hu/LC_MESSAGES/django.po +82 -74
  195. modoboa/locale/it/LC_MESSAGES/django.mo +0 -0
  196. modoboa/locale/it/LC_MESSAGES/django.po +148 -122
  197. modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
  198. modoboa/locale/ja_JP/LC_MESSAGES/django.po +80 -72
  199. modoboa/locale/ka/LC_MESSAGES/django.po +82 -74
  200. modoboa/locale/nl_NL/LC_MESSAGES/django.po +160 -132
  201. modoboa/locale/no/LC_MESSAGES/django.po +82 -74
  202. modoboa/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  203. modoboa/locale/pl_PL/LC_MESSAGES/django.po +172 -149
  204. modoboa/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  205. modoboa/locale/pt_BR/LC_MESSAGES/django.po +172 -144
  206. modoboa/locale/pt_PT/LC_MESSAGES/django.po +135 -112
  207. modoboa/locale/ro_RO/LC_MESSAGES/django.po +87 -75
  208. modoboa/locale/ru/LC_MESSAGES/django.po +142 -118
  209. modoboa/locale/si/LC_MESSAGES/django.po +82 -74
  210. modoboa/locale/sk/LC_MESSAGES/django.po +82 -74
  211. modoboa/locale/sk_SK/LC_MESSAGES/django.po +84 -76
  212. modoboa/locale/sl_SI/LC_MESSAGES/django.po +90 -76
  213. modoboa/locale/sv/LC_MESSAGES/django.mo +0 -0
  214. modoboa/locale/sv/LC_MESSAGES/django.po +172 -139
  215. modoboa/locale/tr/LC_MESSAGES/django.po +87 -75
  216. modoboa/locale/tr_TR/LC_MESSAGES/django.po +84 -74
  217. modoboa/locale/uk/LC_MESSAGES/django.po +82 -74
  218. modoboa/locale/zh/LC_MESSAGES/django.po +82 -74
  219. modoboa/locale/zh_CN/LC_MESSAGES/django.po +82 -74
  220. modoboa/locale/zh_TW/LC_MESSAGES/django.po +87 -75
  221. modoboa/parameters/api/v2/tests.py +2 -2
  222. modoboa/parameters/api/v2/viewsets.py +2 -0
  223. modoboa/policyd/tests.py +2 -0
  224. modoboa/urls_api_v2.py +6 -0
  225. {modoboa-2.4.11.dist-info → modoboa-2.5.0.dist-info}/METADATA +6 -4
  226. {modoboa-2.4.11.dist-info → modoboa-2.5.0.dist-info}/RECORD +231 -181
  227. modoboa/frontend_dist/assets/AccountAliasForm-BV6KvTu6.js +0 -1
  228. modoboa/frontend_dist/assets/AccountEditView-DDOFyfBD.js +0 -1
  229. modoboa/frontend_dist/assets/AccountLayout-rX51xgxT.js +0 -1
  230. modoboa/frontend_dist/assets/AccountPasswordSubForm-D9S6LaeH.js +0 -1
  231. modoboa/frontend_dist/assets/AccountView-cmvaZNq3.js +0 -1
  232. modoboa/frontend_dist/assets/AddressBook-DCJxL8SU.js +0 -1
  233. modoboa/frontend_dist/assets/AdminLayout-r0wfG2lO.js +0 -1
  234. modoboa/frontend_dist/assets/AlarmsView-Bheey-gp.css +0 -1
  235. modoboa/frontend_dist/assets/AlarmsView-C0bqC4PA.js +0 -1
  236. modoboa/frontend_dist/assets/AliasEditView-DVoWoCGY.js +0 -1
  237. modoboa/frontend_dist/assets/AliasView-DrONZXOh.js +0 -1
  238. modoboa/frontend_dist/assets/AuditTrailView-OTkoZaMU.js +0 -1
  239. modoboa/frontend_dist/assets/CalendarView-CqF4_Ui9.js +0 -1
  240. modoboa/frontend_dist/assets/ComposeEmailForm-DO5_GB3e.js +0 -1
  241. modoboa/frontend_dist/assets/ComposeEmailView-A91HCBsN.js +0 -1
  242. modoboa/frontend_dist/assets/ConfirmDialog-BBcgdAnO.js +0 -1
  243. modoboa/frontend_dist/assets/ConnectedLayout-1oRW-Rql.js +0 -1
  244. modoboa/frontend_dist/assets/CreationForm-71YJbjsA.js +0 -1
  245. modoboa/frontend_dist/assets/DashboardView-CdLpSfUl.js +0 -1
  246. modoboa/frontend_dist/assets/DomainAdminList-BjC4KsqI.js +0 -1
  247. modoboa/frontend_dist/assets/DomainEditView-CQjKwYxl.js +0 -1
  248. modoboa/frontend_dist/assets/DomainView-BhhuZI_N.js +0 -5
  249. modoboa/frontend_dist/assets/DomainsView-Cft4BP8Z.js +0 -1
  250. modoboa/frontend_dist/assets/DomainsView-DasJ0NdZ.css +0 -1
  251. modoboa/frontend_dist/assets/EmailField-C8umy0EU.js +0 -1
  252. modoboa/frontend_dist/assets/EmailView-ki7uEQPD.js +0 -1
  253. modoboa/frontend_dist/assets/EmptyLayout-DaA1XH9n.js +0 -1
  254. modoboa/frontend_dist/assets/FiltersView-FYFZxG4B.js +0 -1
  255. modoboa/frontend_dist/assets/ForwardEmailView-cUbnSYCF.js +0 -1
  256. modoboa/frontend_dist/assets/IdentitiesView-njNo8N5n.js +0 -1
  257. modoboa/frontend_dist/assets/InformationView-D1h38POt.js +0 -1
  258. modoboa/frontend_dist/assets/InformationView-U5Ww-Sx1.css +0 -1
  259. modoboa/frontend_dist/assets/MailboxView-IlrLWm_H.js +0 -1
  260. modoboa/frontend_dist/assets/MenuItems-BAtHWzAE.js +0 -1
  261. modoboa/frontend_dist/assets/MessagesView-OSpjixFq.js +0 -1
  262. modoboa/frontend_dist/assets/MigrationsView-DKNOsVzF.js +0 -1
  263. modoboa/frontend_dist/assets/ParametersView-C4bXASiq.js +0 -1
  264. modoboa/frontend_dist/assets/ParametersView-CYXgNmc1.js +0 -1
  265. modoboa/frontend_dist/assets/ProviderEditView-CyxCWTST.js +0 -1
  266. modoboa/frontend_dist/assets/ProviderGeneralForm-BYPjNHqB.js +0 -1
  267. modoboa/frontend_dist/assets/ProvidersView-CxrMkRyk.js +0 -1
  268. modoboa/frontend_dist/assets/ReplyEmailView-Dkw9-N26.js +0 -1
  269. modoboa/frontend_dist/assets/ResourcesForm-CuUvrOdY.js +0 -1
  270. modoboa/frontend_dist/assets/StatisticsView-BN7QsZMT.js +0 -1
  271. modoboa/frontend_dist/assets/TimeSerieChart-BMN8BeFZ.js +0 -1
  272. modoboa/frontend_dist/assets/UserLayout-B6-JQg4F.js +0 -1
  273. modoboa/frontend_dist/assets/VAlert-DIQTrRif.js +0 -1
  274. modoboa/frontend_dist/assets/VApp-CpkYA7js.js +0 -1
  275. modoboa/frontend_dist/assets/VAutocomplete-C4IpXyl8.js +0 -1
  276. modoboa/frontend_dist/assets/VAvatar-Lpb-Dion.js +0 -1
  277. modoboa/frontend_dist/assets/VCard-er_isjE_.js +0 -1
  278. modoboa/frontend_dist/assets/VCheckbox-D-u8JXP1.js +0 -1
  279. modoboa/frontend_dist/assets/VChip-B4iSpj8_.js +0 -1
  280. modoboa/frontend_dist/assets/VColorPicker-BAjGDsXv.js +0 -1
  281. modoboa/frontend_dist/assets/VDataTable-4JRjbtgF.js +0 -1
  282. modoboa/frontend_dist/assets/VDataTableServer-tIDT1m3-.js +0 -1
  283. modoboa/frontend_dist/assets/VDataTableVirtual-BlnO18u_.js +0 -1
  284. modoboa/frontend_dist/assets/VExpansionPanels-CwGtXDhr.js +0 -1
  285. modoboa/frontend_dist/assets/VFileInput-D1_7ZkO_.js +0 -1
  286. modoboa/frontend_dist/assets/VForm-DAkW4nfy.js +0 -1
  287. modoboa/frontend_dist/assets/VMenu-BPFJwj2f.js +0 -1
  288. modoboa/frontend_dist/assets/VPicker-CfT82M8N.js +0 -1
  289. modoboa/frontend_dist/assets/VProgressCircular-w75-3ogi.js +0 -1
  290. modoboa/frontend_dist/assets/VRadioGroup-0j6DNC_k.js +0 -1
  291. modoboa/frontend_dist/assets/VSelect-Cs4ARbAS.js +0 -1
  292. modoboa/frontend_dist/assets/VSelectionControl-Dg-XyRRS.js +0 -1
  293. modoboa/frontend_dist/assets/VSheet-Btq_Mu4s.js +0 -1
  294. modoboa/frontend_dist/assets/VSpacer-C7xukQmu.js +0 -1
  295. modoboa/frontend_dist/assets/VSwitch-Cs1NQrmk.js +0 -1
  296. modoboa/frontend_dist/assets/VTable-CNz2SGk4.js +0 -1
  297. modoboa/frontend_dist/assets/VTabs-B1fyVn4M.js +0 -1
  298. modoboa/frontend_dist/assets/VTextField-BdyvgvkG.js +0 -1
  299. modoboa/frontend_dist/assets/VTextField-C-J20yj_.css +0 -1
  300. modoboa/frontend_dist/assets/VTextarea-DnOMpe0Q.js +0 -1
  301. modoboa/frontend_dist/assets/VToolbar-BiCiBxBJ.js +0 -1
  302. modoboa/frontend_dist/assets/VWindowItem-ChWm_kz3.js +0 -1
  303. modoboa/frontend_dist/assets/WebmailLayout-o4uEkp9e.js +0 -1
  304. modoboa/frontend_dist/assets/forwardRefs-Dvjn_Xq4.js +0 -1
  305. modoboa/frontend_dist/assets/global.store-BaiD63EN.js +0 -1
  306. modoboa/frontend_dist/assets/index-I1VDlN4g.js +0 -984
  307. modoboa/frontend_dist/assets/layout-D8ZJPiJ_.js +0 -1
  308. modoboa/frontend_dist/assets/permissions-CITHLHVg.js +0 -1
  309. modoboa/frontend_dist/assets/transports-Dz7c6kIy.js +0 -1
  310. {modoboa-2.4.11.data → modoboa-2.5.0.data}/scripts/modoboa-admin.py +0 -0
  311. {modoboa-2.4.11.dist-info → modoboa-2.5.0.dist-info}/WHEEL +0 -0
  312. {modoboa-2.4.11.dist-info → modoboa-2.5.0.dist-info}/entry_points.txt +0 -0
  313. {modoboa-2.4.11.dist-info → modoboa-2.5.0.dist-info}/licenses/LICENSE +0 -0
  314. {modoboa-2.4.11.dist-info → modoboa-2.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,265 @@
1
+ """Amavis viewsets."""
2
+
3
+ from django.http import Http404
4
+ from django.shortcuts import get_object_or_404
5
+ from django.utils.translation import gettext as _
6
+
7
+ import django_rq
8
+ from rest_framework import filters, mixins, permissions, response, viewsets
9
+ from rest_framework.decorators import action
10
+
11
+ from modoboa.amavis.sql_connector import SQLconnector
12
+ from modoboa.lib.paginator import Paginator
13
+ from modoboa.lib.permissions import CanViewDomain
14
+
15
+ from modoboa.admin import models as admin_models
16
+ from modoboa.amavis import models, serializers, tasks
17
+ from modoboa.amavis.lib import (
18
+ AMrelease,
19
+ manual_learning_enabled,
20
+ SelfServiceAuthentication,
21
+ )
22
+ from modoboa.amavis.sql_email import SQLemail
23
+ from modoboa.amavis.utils import smart_str
24
+ from modoboa.parameters import tools as param_tools
25
+
26
+
27
+ SELFSERVICE_ACTIONS = ["retrieve", "headers", "delete", "release"]
28
+
29
+
30
+ def get_user_valid_addresses(user):
31
+ """Retrieve all valid addresses of a user."""
32
+ valid_addresses = []
33
+ if user.role == "SimpleUsers":
34
+ valid_addresses.append(user.email)
35
+ try:
36
+ mb = admin_models.Mailbox.objects.get(user=user)
37
+ except admin_models.Mailbox.DoesNotExist:
38
+ pass
39
+ else:
40
+ valid_addresses += mb.alias_addresses
41
+ return valid_addresses
42
+
43
+
44
+ class QuarantineViewSet(viewsets.GenericViewSet):
45
+
46
+ filter_backends = (filters.OrderingFilter,)
47
+ ordering_fields = [
48
+ "datetime",
49
+ "from_address",
50
+ "score",
51
+ "subject",
52
+ "to_address",
53
+ "type",
54
+ ]
55
+ permission_classes = (permissions.IsAuthenticated,)
56
+
57
+ def get_serializer_class(self):
58
+ if self.action == "retrieve":
59
+ return serializers.MessageSerializer
60
+ if self.action == "headers":
61
+ return serializers.MessageHeadersSerializer
62
+ if self.action == "mark_selection":
63
+ return serializers.MarkMessageSelectionSerializer
64
+ if self.action in ["release_selection", "delete_selection"]:
65
+ return serializers.MessageSelectionSerializer
66
+ if self.action in ["delete", "release"]:
67
+ return serializers.MessageIdentifierSerializer
68
+ return serializers.PaginatedMessageListSerializer
69
+
70
+ def get_authenticators(self):
71
+ result = [auth() for auth in self.authentication_classes]
72
+ if self.request:
73
+ action = self.action_map.get(self.request.method.lower())
74
+ if action in SELFSERVICE_ACTIONS:
75
+ result = [SelfServiceAuthentication()] + result
76
+ return result
77
+
78
+ def get_permissions(self):
79
+ if self.request.auth == "selfservice" and self.action in SELFSERVICE_ACTIONS:
80
+ return []
81
+ return super().get_permissions()
82
+
83
+ def list(self, request):
84
+ ordering = request.GET.get("ordering")
85
+ connector = SQLconnector(user=request.user, ordering=ordering)
86
+ try:
87
+ page_size = int(request.GET.get("page_size"))
88
+ except (TypeError, ValueError):
89
+ page_size = request.user.parameters.get_value("messages_per_page")
90
+ total = connector.messages_count(request)
91
+ paginator = Paginator(total, page_size)
92
+ page_num = int(request.GET.get("page", 1))
93
+ page = paginator.getpage(page_num)
94
+ if not page:
95
+ serializer = self.get_serializer(
96
+ {
97
+ "count": 0,
98
+ "first_index": 0,
99
+ "last_index": 0,
100
+ "prev_page": None,
101
+ "next_page": None,
102
+ "results": [],
103
+ }
104
+ )
105
+ return response.Response(serializer.data)
106
+ email_list = connector.fetch(page.id_start, page.id_stop)
107
+ serializer = self.get_serializer(
108
+ {
109
+ "count": total,
110
+ "first_index": page_num * page_size,
111
+ "last_index": (page_num * page_size) + len(email_list),
112
+ "prev_page": page.previous_page_number if page.has_previous else None,
113
+ "next_page": page.next_page_number if page.has_next else None,
114
+ "results": email_list,
115
+ }
116
+ )
117
+ return response.Response(serializer.data)
118
+
119
+ def retrieve(self, request, pk):
120
+ rcpt = request.GET.get("rcpt")
121
+ if rcpt is None:
122
+ return response.Response({"error": _("Invalid request")}, status=400)
123
+ if request.user:
124
+ if request.user.email == rcpt:
125
+ SQLconnector().set_msgrcpt_status(rcpt, pk, "V")
126
+ elif hasattr(request.user, "mailbox"):
127
+ mb = request.user.mailbox
128
+ if rcpt == mb.full_address or rcpt in mb.alias_addresses:
129
+ SQLconnector().set_msgrcpt_status(rcpt, pk, "V")
130
+ mail = SQLemail(pk.encode("ascii"), dformat="plain")
131
+ serializer = self.get_serializer(mail)
132
+ return response.Response(serializer.data)
133
+
134
+ @action(methods=["get"], detail=True)
135
+ def headers(self, request, pk):
136
+ email = SQLemail(pk.encode("ascii"))
137
+ headers = []
138
+ for name in email.msg.keys():
139
+ headers.append({"name": name, "value": email.get_header(email.msg, name)})
140
+ serializer = self.get_serializer({"headers": headers})
141
+ return response.Response(serializer.data)
142
+
143
+ def _release_selection(self, request, selection):
144
+ connector = SQLconnector()
145
+ valid_addresses = None
146
+ if request.user:
147
+ valid_addresses = get_user_valid_addresses(request.user)
148
+ msgrcpts = []
149
+ for item in selection:
150
+ if valid_addresses and item["rcpt"] not in valid_addresses:
151
+ continue
152
+ msgrcpts += [
153
+ (
154
+ item["mailid"],
155
+ connector.get_recipient_message(item["rcpt"], item["mailid"]),
156
+ )
157
+ ]
158
+ if (
159
+ not request.user or request.user.role == "SimpleUsers"
160
+ ) and not param_tools.get_global_parameter("user_can_release"):
161
+ for i, msgrcpt in msgrcpts:
162
+ connector.set_msgrcpt_status(smart_str(msgrcpt.rid.email), i, "p")
163
+ return response.Response({"status": "pending"})
164
+
165
+ amr = AMrelease()
166
+ error = None
167
+ for mid, rcpt in msgrcpts:
168
+ # we can't use the .mail relation on rcpt because it leads to
169
+ # an error on Postgres (memoryview pickle error).
170
+ mail = models.Msgs.objects.get(pk=mid.encode("ascii"))
171
+ result = amr.sendreq(mid, mail.secret_id, rcpt.rid.email)
172
+ if result:
173
+ connector.set_msgrcpt_status(smart_str(rcpt.rid.email), mid, "R")
174
+ else:
175
+ error = result
176
+ break
177
+
178
+ if error:
179
+ return response.Response({"status": error})
180
+
181
+ return response.Response({"status": "released"})
182
+
183
+ @action(methods=["post"], detail=False)
184
+ def release_selection(self, request):
185
+ serializer = self.get_serializer(data=request.data)
186
+ serializer.is_valid(raise_exception=True)
187
+ return self._release_selection(request, serializer.validated_data["selection"])
188
+
189
+ @action(methods=["post"], detail=True)
190
+ def release(self, request, pk):
191
+ serializer = self.get_serializer(data=request.data)
192
+ serializer.is_valid(raise_exception=True)
193
+ return self._release_selection(request, [serializer.validated_data])
194
+
195
+ def _delete_selection(self, request, selection):
196
+ connector = SQLconnector()
197
+ valid_addresses = None
198
+ if request.user:
199
+ valid_addresses = get_user_valid_addresses(request.user)
200
+ for item in selection:
201
+ if valid_addresses and item["rcpt"] not in valid_addresses:
202
+ continue
203
+ connector.set_msgrcpt_status(item["rcpt"], item["mailid"], "D")
204
+ return response.Response(status=204)
205
+
206
+ @action(methods=["post"], detail=False)
207
+ def delete_selection(self, request):
208
+ serializer = self.get_serializer(data=request.data)
209
+ serializer.is_valid(raise_exception=True)
210
+ return self._delete_selection(request, serializer.validated_data["selection"])
211
+
212
+ @action(methods=["post"], detail=True)
213
+ def delete(self, request, pk):
214
+ serializer = self.get_serializer(data=request.data)
215
+ serializer.is_valid(raise_exception=True)
216
+ return self._delete_selection(request, [serializer.validated_data])
217
+
218
+ @action(methods=["post"], detail=False)
219
+ def mark_selection(self, request):
220
+ serializer = self.get_serializer(data=request.data)
221
+ serializer.is_valid(raise_exception=True)
222
+ if not manual_learning_enabled(request.user):
223
+ return response.Response({"status": "ok"})
224
+ recipient_db = serializer.validated_data.get("database")
225
+ if not recipient_db:
226
+ recipient_db = "user" if request.user.role == "SimpleUsers" else "global"
227
+ queue = django_rq.get_queue("modoboa")
228
+ queue.enqueue(
229
+ tasks.manual_learning,
230
+ request.user.pk,
231
+ serializer.validated_data["type"],
232
+ serializer.validated_data["selection"],
233
+ recipient_db,
234
+ )
235
+ return response.Response(status=204)
236
+
237
+
238
+ class PolicyViewSet(
239
+ mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet
240
+ ):
241
+
242
+ permission_classes = (permissions.IsAuthenticated, CanViewDomain)
243
+ serializer_class = serializers.PolicySerializer
244
+
245
+ def get_queryset(self):
246
+ domains = [
247
+ f"@{name}"
248
+ for name in admin_models.Domain.objects.get_for_admin(
249
+ self.request.user
250
+ ).values_list("name", flat=True)
251
+ ]
252
+ return models.Policy.objects.filter(users__email__in=domains)
253
+
254
+ def get_object(self):
255
+ """Return the object the view is displaying."""
256
+ domain = get_object_or_404(admin_models.Domain, pk=self.kwargs["pk"])
257
+ queryset = self.filter_queryset(self.get_queryset())
258
+ obj = queryset.filter(users__email=f"@{domain.name}").first()
259
+ if obj is None:
260
+ raise Http404
261
+
262
+ # May raise a permission denied
263
+ self.check_object_permissions(self.request, obj)
264
+
265
+ return obj
@@ -83,10 +83,12 @@ class CheckPasswordTFASerializer(serializers.Serializer):
83
83
  def validate_password(self, value):
84
84
  user = self.context["user"]
85
85
  if not user.check_password(value):
86
- logger.warning(
87
- _("Failed TFA settings editing attempt from '%s' as user '%s'"),
88
- self.context["remote_addr"],
89
- escape(user.username),
90
- )
86
+ msg = _(
87
+ "Failed TFA settings editing attempt from '%(addr)s' as user '%(user)s'"
88
+ ) % {
89
+ "addr": self.context["remote_addr"],
90
+ "user": escape(user.username),
91
+ }
92
+ logger.warning(msg)
91
93
  raise serializers.ValidationError(_("Invalid password"))
92
94
  return value
@@ -664,9 +664,11 @@ class NotificationSerializer(serializers.Serializer):
664
664
  """Serializer used to render a notification."""
665
665
 
666
666
  id = serializers.CharField()
667
- url = serializers.CharField(required=False)
668
667
  text = serializers.CharField()
669
- level = serializers.CharField()
668
+ color = serializers.CharField()
669
+ target = serializers.CharField()
670
+ url = serializers.CharField(required=False)
671
+ counter = serializers.IntegerField(required=False)
670
672
 
671
673
 
672
674
  class ModoboaApplicationSerializer(serializers.Serializer):
@@ -196,6 +196,18 @@ class AccountViewSetTestCase(ModoAPITestCase):
196
196
  me = resp.json()
197
197
  self.assertEqual(me["username"], "admin")
198
198
 
199
+ def test_update_me(self):
200
+ url = reverse("v2:account-me")
201
+ data = {
202
+ "first_name": "First name",
203
+ "secondary_email": "toto@iti.com",
204
+ "language": "fr",
205
+ }
206
+ resp = self.client.put(url, data, format="json")
207
+ self.assertEqual(resp.status_code, 200)
208
+ me = resp.json()
209
+ self.assertEqual(me["secondary_email"], data["secondary_email"])
210
+
199
211
  def test_me_password(self, password_ko="Toto1234", password_ok="password"):
200
212
  url = reverse("v2:account-check-me-password")
201
213
  resp = self.client.post(url, {"password": password_ko}, format="json")
@@ -271,22 +283,22 @@ class AccountViewSetTestCase(ModoAPITestCase):
271
283
  url = reverse("v2:account-available-applications")
272
284
  resp = self.client.get(url)
273
285
  self.assertEqual(resp.status_code, 200)
274
- # admin -> only 1 app.
275
- self.assertEqual(len(resp.json()), 1)
286
+ # admin -> only 2 apps.
287
+ self.assertEqual(len(resp.json()), 2)
276
288
 
277
289
  # Domain admin with mailbox
278
290
  dadmin = models.User.objects.get(username="admin@test.com")
279
291
  self.authenticate_user(dadmin)
280
292
  resp = self.client.get(url)
281
293
  self.assertEqual(resp.status_code, 200)
282
- self.assertEqual(len(resp.json()), 4)
294
+ self.assertEqual(len(resp.json()), 5)
283
295
 
284
296
  # Simple user
285
297
  user = models.User.objects.get(username="user@test.com")
286
298
  self.authenticate_user(user)
287
299
  resp = self.client.get(url)
288
300
  self.assertEqual(resp.status_code, 200)
289
- self.assertEqual(len(resp.json()), 3)
301
+ self.assertEqual(len(resp.json()), 4)
290
302
 
291
303
  @override_settings(
292
304
  MODOBOA_APPS=[
@@ -32,11 +32,6 @@ urlpatterns += [
32
32
  views.ComponentsInformationAPIView.as_view(),
33
33
  name="components_information",
34
34
  ),
35
- path(
36
- "admin/notifications/",
37
- views.NotificationsAPIView.as_view(),
38
- name="notifications",
39
- ),
40
35
  path(
41
36
  "admin/news_feed/",
42
37
  views.NewsFeedAPIView.as_view(),
@@ -48,5 +43,10 @@ urlpatterns += [
48
43
  name="statistics",
49
44
  ),
50
45
  path("capabilities/", views.CapabilitiesAPIView.as_view(), name="capabilities"),
46
+ path(
47
+ "notifications/",
48
+ views.NotificationsAPIView.as_view(),
49
+ name="notifications",
50
+ ),
51
51
  path("theme/", views.ThemeAPIView.as_view(), name="theme"),
52
52
  ]
@@ -142,7 +142,9 @@ class PasswordResetConfirmView(APIView):
142
142
  class ComponentsInformationAPIView(APIView):
143
143
  """Retrieve information about installed components."""
144
144
 
145
- permission_classes = [permissions.IsAuthenticated, IsSuperUser]
145
+ permission_classes = [
146
+ permissions.IsAuthenticated,
147
+ ]
146
148
  throttle_classes = [UserLesserDdosUser]
147
149
 
148
150
  @extend_schema(responses=serializers.ModoboaComponentSerializer(many=True))
@@ -48,11 +48,22 @@ def create_static_tokens(request):
48
48
  class AccountViewSet(core_v1_viewsets.AccountViewSet):
49
49
  """Account viewset."""
50
50
 
51
- @extend_schema(responses=admin_v2_serializers.AccountMeSerializer)
52
- @action(methods=["get"], detail=False)
51
+ @extend_schema(
52
+ responses=admin_v2_serializers.AccountMeSerializer,
53
+ request=admin_v2_serializers.AccountMeUpdateSerializer,
54
+ )
55
+ @action(methods=["get", "put"], detail=False)
53
56
  def me(self, request):
54
57
  """Return information about connected user."""
55
- serializer = admin_v1_serializers.AccountSerializer(request.user)
58
+ if request.method == "PUT":
59
+ serializer = admin_v2_serializers.AccountMeUpdateSerializer(
60
+ data=request.data, instance=request.user
61
+ )
62
+ serializer.is_valid(raise_exception=True)
63
+ instance = serializer.save()
64
+ serializer = admin_v1_serializers.AccountSerializer(instance)
65
+ else:
66
+ serializer = admin_v1_serializers.AccountSerializer(request.user)
56
67
  return response.Response(serializer.data)
57
68
 
58
69
  @action(
@@ -166,6 +177,16 @@ class AccountViewSet(core_v1_viewsets.AccountViewSet):
166
177
  "url": "/admin",
167
178
  }
168
179
  )
180
+ if "modoboa.amavis" in settings.MODOBOA_APPS:
181
+ apps.append(
182
+ {
183
+ "name": "amavis",
184
+ "label": _("Quarantine"),
185
+ "icon": "mdi-server-security",
186
+ "description": _("Amavis quarantine"),
187
+ "url": "/user/quarantine",
188
+ }
189
+ )
169
190
  if hasattr(request.user, "mailbox"):
170
191
  if "modoboa.contacts" in settings.MODOBOA_APPS:
171
192
  apps += [
modoboa/core/handlers.py CHANGED
@@ -125,7 +125,9 @@ def check_for_new_versions(sender, include_all: bool, **kwargs) -> list:
125
125
  {
126
126
  "id": "newversionavailable",
127
127
  "text": _("One or more updates are available"),
128
- "level": "info",
128
+ "color": "info",
129
+ "url": "/admin/information",
130
+ "target": "admin",
129
131
  }
130
132
  ]
131
133
  elif include_all:
@@ -137,7 +139,9 @@ def check_for_new_versions(sender, include_all: bool, **kwargs) -> list:
137
139
  "id": "deprecatedpasswordscheme",
138
140
  "text": _("You are still using a deprecated password scheme (%s)")
139
141
  % hasher.name,
140
- "level": "warning",
142
+ "color": "warning",
143
+ "url": "/admin/information",
144
+ "target": "admin",
141
145
  }
142
146
  ]
143
147
  return result
@@ -0,0 +1,33 @@
1
+ from django.core.management.base import BaseCommand, CommandError
2
+
3
+ from oauth2_provider.models import get_application_model
4
+
5
+
6
+ class Command(BaseCommand):
7
+ """Command class."""
8
+
9
+ help = "Add new allowed hosts to frontend Oauth2 application."
10
+
11
+ def add_arguments(self, parser):
12
+ parser.add_argument("hostnames", type=str, nargs="+")
13
+
14
+ def handle(self, *args, **options):
15
+ app_model = get_application_model()
16
+ app = app_model.objects.filter(name="modoboa_frontend").first()
17
+ if not app:
18
+ raise CommandError(
19
+ "Application modoboa_frontend not found. "
20
+ "Make sure you ran load_initial_data first."
21
+ )
22
+ redirect_uris = app.redirect_uris.split(" ")
23
+ post_logout_redirect_uris = app.post_logout_redirect_uris.split(" ")
24
+ for hostname in options["hostnames"]:
25
+ uri = f"https://{hostname}/login/logged"
26
+ if uri not in redirect_uris:
27
+ redirect_uris.append(uri)
28
+ uri = f"https://{hostname}"
29
+ if uri not in post_logout_redirect_uris:
30
+ post_logout_redirect_uris.append(uri)
31
+ app.redirect_uris = " ".join(redirect_uris)
32
+ app.post_logout_redirect_uris = " ".join(post_logout_redirect_uris)
33
+ app.save()
@@ -192,3 +192,13 @@ class Command(BaseCommand):
192
192
  }}
193
193
  """
194
194
  )
195
+
196
+
197
+ # ADD SIGNAL FOR THAT
198
+ # def load_initial_data(self):
199
+ # """Create records for existing domains and co."""
200
+ # for dom in Domain.objects.all():
201
+ # policy = create_user_and_policy("@{0}".format(dom.name))
202
+ # for domalias in dom.domainalias_set.all():
203
+ # domalias_pattern = "@{0}".format(domalias.name)
204
+ # create_user_and_use_policy(domalias_pattern, policy)
@@ -3,15 +3,33 @@
3
3
  from django.db import migrations
4
4
 
5
5
 
6
+ class RenameIndexIfExists(migrations.RenameIndex):
7
+
8
+ def database_forwards(self, app_label, schema_editor, from_state, to_state):
9
+ from_model = from_state.apps.get_model(app_label, self.model_name)
10
+ columns = [
11
+ from_model._meta.get_field(field).column for field in ["email", "is_active"]
12
+ ]
13
+ matching_index_name = schema_editor._constraint_names(
14
+ from_model,
15
+ column_names=columns,
16
+ index=True,
17
+ unique=False,
18
+ )
19
+ if len(matching_index_name) != 1:
20
+ return
21
+ super().database_forwards(app_label, schema_editor, from_state, to_state)
22
+
23
+
6
24
  class Migration(migrations.Migration):
7
25
  dependencies = [
8
26
  ("core", "0024_alter_user_language"),
9
27
  ]
10
28
 
11
29
  operations = [
12
- # migrations.RenameIndex(
13
- # model_name="user",
14
- # new_name="core_user_email_c0c03f_idx",
15
- # old_fields=("email", "is_active"),
16
- # ),
30
+ RenameIndexIfExists(
31
+ model_name="user",
32
+ new_name="core_user_email_c0c03f_idx",
33
+ old_fields=("email", "is_active"),
34
+ ),
17
35
  ]
@@ -186,6 +186,30 @@ class ManagementCommandsTestCase(SimpleModoTestCase):
186
186
  with self.assertRaises(models.User.DoesNotExist):
187
187
  account.refresh_from_db()
188
188
 
189
+ def test_add_allowed_hosts(self):
190
+ with self.assertRaises(management.CommandError):
191
+ management.call_command(
192
+ "add_allowed_hosts", "app1.domain.tld", "app2.domain.tld"
193
+ )
194
+ management.call_command("load_initial_data")
195
+ management.call_command(
196
+ "add_allowed_hosts", "app1.domain.tld", "app2.domain.tld"
197
+ )
198
+ app_model = get_application_model()
199
+ app = app_model.objects.filter(name="modoboa_frontend").first()
200
+ self.assertIn("app1.domain.tld", app.redirect_uris)
201
+ self.assertIn("app2.domain.tld", app.redirect_uris)
202
+ self.assertIn("app1.domain.tld", app.post_logout_redirect_uris)
203
+ self.assertIn("app2.domain.tld", app.post_logout_redirect_uris)
204
+
205
+ # Check if same hostname is not added more than once
206
+ uri_count = len(app.redirect_uris.split(" "))
207
+ management.call_command(
208
+ "add_allowed_hosts", "app1.domain.tld", "app2.domain.tld"
209
+ )
210
+ app.refresh_from_db()
211
+ self.assertEqual(len(app.redirect_uris.split(" ")), uri_count)
212
+
189
213
 
190
214
  class APICommunicationTestCase(ModoTestCase):
191
215
  """Check communication with the API."""
modoboa/core/utils.py CHANGED
@@ -111,6 +111,9 @@ def get_capabilities():
111
111
  if is_rspamd_installed:
112
112
  rspamd_options = get_rspamd_options()
113
113
  capabilities.update({"rspamd": rspamd_options})
114
+ # Amavis
115
+ if "modoboa.amavis" in settings.MODOBOA_APPS:
116
+ capabilities.update({"amavis": {}})
114
117
  # IMAP migration
115
118
  if "modoboa.imap_migration" in settings.MODOBOA_APPS:
116
119
  capabilities.update({"imap_migration": {}})
@@ -0,0 +1 @@
1
+ import{C as q}from"./ChoiceField-B3ReQHVe.js";import{u as M,b as S,g as V,h as f,i as _,c as R,o as y,w as h,a as d,f as s,e as U,t as x,k as N,_ as C,z as D,m as j,l as Q,F as T,s as G}from"./index-BImkz5Jx.js";import{V as H,a as L}from"./VRow-ozg66L7j.js";import{V as B}from"./VAlert-BuaaYN2h.js";import{c as P}from"./VAvatar-Cmga0vj6.js";import{V as k,r as F}from"./VForm-D5iPGkde.js";import{_ as z}from"./AccountPasswordSubForm-DZGt_Xgq.js";import{E as I}from"./EmailField-CwcwI5xW.js";import{V as E}from"./VTextField-XoGTj1KG.js";import{V as $}from"./VSwitch-DwxdeAEq.js";import{u as K}from"./domains.store-D-vWCEIK.js";import{a as Y}from"./accounts-DUzbx6k8.js";import{V as J}from"./VChip-D_styETR.js";const W={class:"headline"},X={class:"mt-4"},Ve={__name:"AccountRoleForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const i=M(),{$gettext:l}=S(),v=g,m=V(),c=f(()=>v.modelValue),a=f(()=>i.authUser),p=f(()=>{const r=b.value.find(n=>n.value===c.value.role);return r!==void 0?r.label:""}),e=f(()=>{const r=b.value.find(n=>n.value===c.value.role);return r!==void 0?r.help:""}),b=f(()=>a.value.role===_.SUPER_ADMIN?[...A,...o,...u,...t]:a.value.role===_.DOMAIN_ADMIN?[...A]:a.value.role===_.RESELLER?[...o,...A]:[]),A=[{label:l("Simple user"),value:_.USER,help:l("A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.")}],o=[{label:l("Domain administrator"),value:_.DOMAIN_ADMIN,help:l("A user with privileges on one or more domain. He will be allowed to administer mailboxes and he can also have a mailbox.")}],u=[{label:l("Reseller"),value:_.RESELLER,help:l("An intermediate user who has the same privileges than a Domain administrator, plus the possibility to create domains and to assign resources.")}],t=[{label:l("Super administrator"),value:_.SUPER_ADMIN,help:l("A user with all privileges, can do anything. By default, such a user does not have a mailbox so he can't access end-user features.")}];return w({vFormRef:m,roleLabel:p}),(r,n)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[d(H,null,{default:h(()=>[d(L,{cols:"7"},{default:h(()=>[d(s(q),{modelValue:c.value.role,"onUpdate:modelValue":n[0]||(n[0]=O=>c.value.role=O),label:s(l)("Choose a role"),choices:b.value,"choices-per-line":2},null,8,["modelValue","label","choices"])]),_:1}),d(L,{cols:"5"},{default:h(()=>[d(B,{color:"primary",class:"mt-11 ml-4 rounded-lg"},{default:h(()=>[U("h3",W,x(p.value),1),U("p",X,x(e.value),1),d(P,{color:"white",class:"float-right",size:"large",icon:"mdi-help-circle-outline"})]),_:1})]),_:1})]),_:1})]),_:1},512))}},Z={class:"m-label"},ee={class:"m-label"},ae={class:"m-label"},he={__name:"AccountGeneralForm",props:{modelValue:{type:Object,default:null},editing:{type:Boolean,default:!1}},setup(g,{expose:w}){const{$gettext:i}=S(),l=M(),v=g,m=V(),c=V({}),a=f(()=>v.modelValue),p=f(()=>a.value.role===_.USER?i("Enter an email address"):i("Enter a simple username or an email address")),e=f(()=>a.value.role===_.USER?"email":"text");function b(){a.value.username.indexOf("@")!==-1&&(a.value.mailbox.full_address=a.value.username,a.value.mailbox.message_limit=null)}return w({vFormRef:m,formErrors:c}),(A,o)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[U("label",Z,x(s(i)("Username")),1),d(I,{ref:"username",modelValue:a.value.username,"onUpdate:modelValue":[o[0]||(o[0]=u=>a.value.username=u),b],placeholder:p.value,type:e.value,rules:e.value==="email"?[s(F).required,s(F).email]:[s(F).required],role:a.value.role,"error-msg":c.value.value?c.value.value.username:[]},null,8,["modelValue","placeholder","type","rules","role","error-msg"]),U("label",ee,x(s(i)("First name")),1),d(E,{modelValue:a.value.first_name,"onUpdate:modelValue":o[1]||(o[1]=u=>a.value.first_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),U("label",ae,x(s(i)("Last name")),1),d(E,{modelValue:a.value.last_name,"onUpdate:modelValue":o[2]||(o[2]=u=>a.value.last_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),s(l).authUser.pk!==a.value.pk?(y(),R(z,{key:0,ref:"passwordForm",modelValue:a.value,"onUpdate:modelValue":o[3]||(o[3]=u=>a.value=u),editing:g.editing,"form-errors":c.value},null,8,["modelValue","editing","form-errors"])):(y(),R(B,{key:1,type:"info",variant:"tonal",class:"py-2"},{default:h(()=>[N(x(s(i)("You can update your password from the Account section")),1)]),_:1})),d($,{modelValue:a.value.is_active,"onUpdate:modelValue":o[4]||(o[4]=u=>a.value.is_active=u),label:s(i)("Enabled"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},le={class:"m-label"},oe={class:"m-label"},te={__name:"AccountMailboxForm",props:{modelValue:{type:Object,default:null}},emits:["update:modelValue"],setup(g,{expose:w,emit:i}){const{$gettext:l}=S(),v=i,m=g,c=K(),a=f(()=>c.domains),p=V(),e=V({mailbox:{}});D(m.modelValue,t=>{t?(e.value={...t},e.value.role===_.USER&&(e.value.mailbox||(e.value.mailbox={}),e.value.mailbox.full_address=e.value.username),e.value.mailbox.message_limit===""&&(e.value.mailbox.message_limit=null)):e.value={mailbox:{}}},{immediate:!0}),D(e,t=>{v("update:modelValue",t)},{deep:!0});const b=f(()=>{const t=e.value.mailbox.full_address;if(t&&t.indexOf("@")!==-1){const r=a.value.find(n=>n.name===t.split("@")[1]);if(r)return parseInt(r.default_mailbox_quota)}}),A=f(()=>{let t=l("Use domain default value");return b.value!==void 0&&(b.value===0?t+=` (${l("unlimited")})`:t+=` (${b.value} MB)`),t});function o(t){t&&delete e.value.mailbox.quota}function u(t){t===""&&(e.value.mailbox.message_limit=null)}return w({vFormRef:p}),(t,r)=>(y(),R(k,{ref_key:"vFormRef",ref:p},{default:h(()=>[U("label",le,x(s(l)("Quota")),1),d($,{modelValue:e.value.mailbox.use_domain_quota,"onUpdate:modelValue":[r[0]||(r[0]=n=>e.value.mailbox.use_domain_quota=n),o],label:A.value,color:"primary"},null,8,["modelValue","label"]),e.value.mailbox.use_domain_quota?j("",!0):(y(),R(E,{key:0,modelValue:e.value.mailbox.quota,"onUpdate:modelValue":r[1]||(r[1]=n=>e.value.mailbox.quota=n),placeholder:s(l)("Ex: 10MB. Leave empty for no limit"),hint:s(l)("Quota for this mailbox, can be expressed in KB, MB (default) or GB. Define a custom value or use domain's default one. Leave empty to define an unlimited value (not allowed for domain administrators)."),"persistent-hint":"",variant:"outlined",density:"compact"},null,8,["modelValue","placeholder","hint"])),U("label",oe,x(s(l)("Daily message sending limit")),1),d(E,{modelValue:e.value.mailbox.message_limit,"onUpdate:modelValue":[r[2]||(r[2]=n=>e.value.mailbox.message_limit=n),u],placeholder:s(l)("Leave empty for no limit"),hint:s(l)("Number of messages this mailbox can send per day. Leave empty for no limit."),"persistent-hint":"",variant:"outlined",density:"compact",rules:[s(F).numericOrNull]},null,8,["modelValue","placeholder","hint","rules"]),d($,{modelValue:e.value.mailbox.is_send_only,"onUpdate:modelValue":r[3]||(r[3]=n=>e.value.mailbox.is_send_only=n),label:s(l)("Send only account"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},xe=C(te,[["__scopeId","data-v-41714779"]]),ye={__name:"AccountAliasForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const{$gettext:i}=S(),l=g,v=f(()=>l.modelValue),m=V(""),c=V(),a=V(),p=V(!1),e=V([]);async function b(){if(e.value=[],p.value=!0,v.value.aliases.indexOf(m.value)!==-1){e.value=[i("Alias already added")],m.value="",p.value=!1;return}try{await Y.validate({aliases:[m.value],mailbox:v.value.mailbox}),v.value.aliases.push(m.value),m.value="",a.value.resetValidation()}catch(o){o.response.data.aliases?o.response.data.aliases:o.response.data.non_field_errors&&o.response.data.non_field_errors}finally{p.value=!1}}function A(o){v.value.aliases.splice(o,1)}return w({vFormRef:a}),(o,u)=>(y(),R(k,{ref_key:"vFormRef",ref:a},{default:h(()=>[d(s(I),{ref_key:"aliasField",ref:c,modelValue:m.value,"onUpdate:modelValue":u[0]||(u[0]=t=>m.value=t),placeholder:s(i)("Start typing a name here..."),hint:s(i)("Alias(es) of this mailbox. To create a catchall alias, just enter the domain name (@domain.tld)."),"persistent-hint":"",loading:p.value,"error-msg":e.value,onDomainSelected:b},null,8,["modelValue","placeholder","hint","loading","error-msg"]),(y(!0),Q(T,null,G(v.value.aliases,(t,r)=>(y(),R(J,{key:r,class:"mr-2 mt-2",closable:"","onClick:close":n=>A(r)},{default:h(()=>[N(x(t),1)]),_:2},1032,["onClick:close"]))),128))]),_:1},512))}};export{xe as A,ye as _,he as a,Ve as b};
@@ -0,0 +1 @@
1
+ import{b as j,u as H,B as J,q as K,g as c,h as P,i as T,d as W,z as X,c as d,l as p,o,a,e as v,w as e,m as k,f as m,k as f,t as r,F as B,_ as Y}from"./index-BImkz5Jx.js";import{b as Z,a as ee,A as ae,_ as le}from"./AccountAliasForm-BuSy_1n9.js";import{_ as te}from"./ResourcesForm-OaqdRYVs.js";import{L as oe}from"./LoadingData-CdVvm4FI.js";import{a as q}from"./accounts-DUzbx6k8.js";import{p as se}from"./parameters-aSQiR7kN.js";import{u as ue}from"./permissions-DNoefz-n.js";import{d as V,c as U,V as L}from"./VAvatar-Cmga0vj6.js";import{V as g,c as b,a as x,b as re}from"./VExpansionPanels-BmH5Jl2Z.js";import{V as _,a as n}from"./VRow-ozg66L7j.js";import{V as ne,a as ie}from"./VToolbar-CFZfqeOr.js";import"./ChoiceField-B3ReQHVe.js";import"./VAlert-BuaaYN2h.js";import"./VProgressCircular-BtOPiGCg.js";import"./tag-B_yWNNJD.js";import"./VForm-D5iPGkde.js";import"./VInput-CoDJzvaW.js";import"./forwardRefs-cvcnlhoK.js";import"./ssrBoot-CKUX4kcb.js";import"./AccountPasswordSubForm-DZGt_Xgq.js";import"./VTextField-XoGTj1KG.js";import"./VSwitch-DwxdeAEq.js";import"./VSelectionControl-zyz-fJvC.js";import"./EmailField-CwcwI5xW.js";import"./domains.store-D-vWCEIK.js";import"./domains-BSawReeu.js";import"./VChip-D_styETR.js";import"./VContainer-B46caNs1.js";/* empty css */const de={key:1},me={key:0},fe={key:0},ce={class:"mr-2"},_e={class:"mr-2"},ve={key:0},pe={class:"mr-2"},ke={class:"mr-2"},ye={class:"mr-2"},Ve={key:0},ge={class:"mr-2"},be={key:0},xe={class:"mr-2"},Fe={class:"mr-2"},we={class:"mt-4 d-flex justify-end"},he={__name:"AccountEditForm",setup(G){const{$gettext:i}=j(),N=H(),F=J(),O=K(),{canSetRole:S}=ue(),l=c({pk:F.params.id}),w=P(()=>l.value.username&&l.value.username.indexOf("@")!==-1),$=P(()=>{const t=C.value.params&&C.value.params.enable_admin_limits&&l.value.role!==T.USER&&l.value.role!==T.SUPER_ADMIN;return t===void 0?!1:t}),C=c({}),h=c(0),y=c(!1),E=c(!1),A=c(),I=c(),M=c(),D=c(),R=c(),Q=P(()=>{const t={roleForm:A,generalForm:I};return w.value&&(t.aliasForm=D,t.mailboxForm=M),$.value&&(t.resourcesForm=R),t});async function z(){E.value=!1,h.value=[];for(const[t,s]of Object.entries(Q.value))if(s.value!=null){const{valid:u}=await s.value.vFormRef.validate();u||(E.value=!0,h.value.push(t))}if(!E.value){y.value=!0;try{const t={...l.value};w.value?t.mailbox.full_address=t.username:t.mailbox&&delete t.mailbox.full_address,t.new_password&&(t.password=t.new_password,delete t.new_password,delete t.password_confirmation),$.value&&R.value!==null&&(t.resources=R.value.getPayload()),t.aliases===null&&delete t.aliases,S.value||(l.value.pk===N.authUser.pk?t.role=N.authUser.role:t.role=T.USER),await q.patch(l.value.pk,t),O.push({name:"AccountDetail",params:{id:F.params.id}})}finally{y.value=!1}}}return W(()=>{q.get(F.params.id).then(t=>{l.value=t.data,y.value=!1}),S.value&&se.getGlobalApplication("limits").then(t=>{C.value=t.data})}),X(l,()=>{delete l.value.domains,l.value.mailbox===null&&delete l.value.mailbox},{deep:!0}),(t,s)=>y.value?(o(),d(oe,{key:0})):(o(),p("div",de,[a(re,{modelValue:h.value,"onUpdate:modelValue":s[5]||(s[5]=u=>h.value=u),multiple:E.value},{default:e(()=>[m(S)?(o(),d(g,{key:0,eager:"",value:"roleForm"},{default:e(()=>[a(b,null,{default:e(({expanded:u})=>[a(_,{"no-gutters":""},{default:e(()=>[a(n,{cols:"4"},{default:e(()=>[f(r(m(i)("Role")),1)]),_:1}),a(n,{cols:"8",class:"text-medium-emphasis"},{default:e(()=>[a(V,{"leave-absolute":""},{default:e(()=>[u?(o(),p("span",me)):(o(),d(_,{key:1,"no-gutters":"",style:{width:"100%"}},{default:e(()=>[A.value?(o(),d(n,{key:0,cols:"6"},{default:e(()=>[f(r(A.value.roleLabel),1)]),_:1})):k("",!0)]),_:1}))]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:1}),a(x,null,{default:e(()=>[a(Z,{ref_key:"roleForm",ref:A,modelValue:l.value,"onUpdate:modelValue":s[0]||(s[0]=u=>l.value=u)},null,8,["modelValue"])]),_:1})]),_:1})):k("",!0),a(g,{eager:"",value:"generalForm"},{default:e(()=>[a(b,null,{default:e(({expanded:u})=>[a(_,{"no-gutters":""},{default:e(()=>[a(n,{cols:"4"},{default:e(()=>[f(r(m(i)("Identification")),1)]),_:1}),a(n,{cols:"8",class:""},{default:e(()=>[a(V,{"leave-absolute":""},{default:e(()=>[u?(o(),p("span",fe)):(o(),d(_,{key:1,"no-gutters":"",style:{width:"100%"}},{default:e(()=>[a(n,{cols:"6"},{default:e(()=>[v("div",ce,r(m(i)("Username:"))+" "+r(l.value.username),1)]),_:1}),a(n,{cols:"6"},{default:e(()=>[v("div",_e,[f(r(m(i)("Enabled"))+" ",1),l.value.is_active?(o(),d(U,{key:0,color:"success"},{default:e(()=>s[7]||(s[7]=[f("mdi-check-circle-outline")])),_:1,__:[7]})):(o(),d(U,{key:1},{default:e(()=>s[8]||(s[8]=[f("mdi-close-circle-outline")])),_:1,__:[8]}))])]),_:1})]),_:1}))]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:1}),a(x,null,{default:e(()=>[a(ee,{ref_key:"generalForm",ref:I,modelValue:l.value,"onUpdate:modelValue":s[1]||(s[1]=u=>l.value=u),editing:""},null,8,["modelValue"])]),_:1})]),_:1}),w.value?(o(),d(g,{key:1,eager:"",value:"mailboxForm"},{default:e(()=>[a(b,null,{default:e(({expanded:u})=>[a(_,{"no-gutters":""},{default:e(()=>[a(n,{cols:"4"},{default:e(()=>[f(r(m(i)("Mailbox")),1)]),_:1}),a(n,{cols:"8",class:""},{default:e(()=>[a(V,{"leave-absolute":""},{default:e(()=>[u?(o(),p("span",ve)):(o(),d(_,{key:1,"no-gutters":"",style:{width:"100%"}},{default:e(()=>[l.value.mailbox?(o(),p(B,{key:0},[l.value.mailbox.use_domain_quota?(o(),d(n,{key:0,cols:"6"},{default:e(()=>[v("div",pe,r(m(i)("Quota: "))+" "+r(m(i)("domain's default value")),1)]),_:1})):(o(),d(n,{key:1,cols:"6"},{default:e(()=>[v("div",ke,r(m(i)("Quota: "))+" "+r(l.value.mailbox.quota),1)]),_:1})),a(n,{cols:"6"},{default:e(()=>[v("div",ye,[f(r(m(i)("Send only"))+" ",1),l.value.mailbox.is_send_only?(o(),d(U,{key:0,color:"success"},{default:e(()=>s[9]||(s[9]=[f("mdi-check-circle-outline")])),_:1,__:[9]})):(o(),d(U,{key:1},{default:e(()=>s[10]||(s[10]=[f("mdi-close-circle-outline")])),_:1,__:[10]}))])]),_:1})],64)):k("",!0)]),_:1}))]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:1}),a(x,null,{default:e(()=>[a(ae,{ref_key:"mailboxForm",ref:M,modelValue:l.value,"onUpdate:modelValue":s[2]||(s[2]=u=>l.value=u)},null,8,["modelValue"])]),_:1})]),_:1})):k("",!0),w.value?(o(),d(g,{key:2,eager:"",value:"aliasForm"},{default:e(()=>[a(b,null,{default:e(({expanded:u})=>[a(_,{"no-gutters":""},{default:e(()=>[a(n,{cols:"4"},{default:e(()=>[f(r(m(i)("Alias")),1)]),_:1}),a(n,{cols:"8",class:""},{default:e(()=>[a(V,{"leave-absolute":""},{default:e(()=>[u?(o(),p("span",Ve)):(o(),d(_,{key:1,"no-gutters":"",style:{width:"100%"}},{default:e(()=>[l.value.aliases?(o(),d(n,{key:0,cols:"6"},{default:e(()=>[v("div",ge,r(m(i)("Number of associated alias:"))+" "+r(l.value.aliases.length),1)]),_:1})):k("",!0)]),_:1}))]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:1}),a(x,null,{default:e(()=>[a(le,{ref_key:"aliasForm",ref:D,modelValue:l.value,"onUpdate:modelValue":s[3]||(s[3]=u=>l.value=u)},null,8,["modelValue"])]),_:1})]),_:1})):k("",!0),$.value?(o(),d(g,{key:3,eager:"",value:"resourcesForm"},{default:e(()=>[a(b,null,{default:e(({expanded:u})=>[a(_,{"no-gutters":""},{default:e(()=>[a(n,{cols:"4"},{default:e(()=>[f(r(m(i)("Resources")),1)]),_:1}),a(n,{cols:"8",class:""},{default:e(()=>[a(V,{"leave-absolute":""},{default:e(()=>[u?(o(),p("span",be)):(o(),d(_,{key:1,"no-gutters":"",style:{width:"100%"}},{default:e(()=>[l.value.resources!=null&&l.value.resources.length==2?(o(),p(B,{key:0},[a(n,{cols:"6"},{default:e(()=>[v("div",xe,r(m(i)("Number of allowed mailboxes:"))+" "+r(l.value.resources[0].max_value),1)]),_:1}),a(n,{cols:"6"},{default:e(()=>[v("div",Fe,r(m(i)("Number of allowed mailbox aliases:"))+" "+r(l.value.resources[1].max_value),1)]),_:1})],64)):k("",!0)]),_:1}))]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:1}),a(x,null,{default:e(()=>[a(te,{ref_key:"resourcesForm",ref:R,modelValue:l.value.resources,"onUpdate:modelValue":s[4]||(s[4]=u=>l.value.resources=u)},null,8,["modelValue"])]),_:1})]),_:1})):k("",!0)]),_:1},8,["modelValue","multiple"]),v("div",we,[a(L,{loading:y.value,variant:"outlined",onClick:s[6]||(s[6]=u=>t.$router.go(-1))},{default:e(()=>[f(r(m(i)("Cancel")),1)]),_:1},8,["loading"]),a(L,{class:"ml-4",color:"primary darken-1",variant:"outlined",loading:y.value,onClick:z},{default:e(()=>[f(r(m(i)("Save")),1)]),_:1},8,["loading"])])]))}},Ee={__name:"AccountEditView",setup(G){const{$gettext:i}=j();return(N,F)=>(o(),p(B,null,[a(ne,{flat:""},{default:e(()=>[a(ie,null,{default:e(()=>[f(r(m(i)("Edit account")),1)]),_:1})]),_:1}),a(he)],64))}},la=Y(Ee,[["__scopeId","data-v-4019c7f8"]]);export{la as default};
@@ -0,0 +1 @@
1
+ import{u as n,b as c,g as u,h as s,c as l,o as f}from"./index-BImkz5Jx.js";import{_}from"./ConnectedLayout-UWjiYBNw.js";import{p as h}from"./parameters-aSQiR7kN.js";import"./VContainer-B46caNs1.js";/* empty css */import"./tag-B_yWNNJD.js";import"./VApp-CKP-6zGP.js";import"./layout-C5FyYCHK.js";import"./ssrBoot-CKUX4kcb.js";import"./logos-BswdveCV.js";import"./layout.store-NXWtFIwL.js";import"./VAvatar-Cmga0vj6.js";import"./VProgressCircular-BtOPiGCg.js";import"./forwardRefs-cvcnlhoK.js";import"./VMenu-gUG70-zD.js";import"./global.store-DbkcI5o2.js";import"./VBadge-CixeK87a.js";import"./VCard-CxH9DWoK.js";import"./VRow-ozg66L7j.js";import"./VSheet-BNx2X4Mk.js";const q={__name:"AccountLayout",setup(d){const i=n(),{$gettext:e}=c(),r=u([]),m=s(()=>i.authUser),p=s(()=>{const t=[],a={contacts:"mdi-contacts-outline",webmail:"mdi-at"};if(t.push({text:e("Settings"),to:{name:"AccountSettings"},icon:"mdi-cog"}),m.value.mailbox){t.push({text:e("Filters"),to:{name:"AccountFilters"},icon:"mdi-filter"});for(const o of r.value)t.push({icon:a[o.name],text:o.label,to:{name:"AccountParametersEdit",params:{app:o.name}}})}return t});return h.getUserApplications().then(t=>{r.value=t.data}),(t,a)=>(f(),l(_,{color:"grey","menu-items":p.value},null,8,["menu-items"]))}};export{q as default};
@@ -0,0 +1 @@
1
+ import{b as P,n as _,h as b,l as m,o as n,m as p,a as o,F as V,e as v,t as y,f as l,w as u,c as h}from"./index-BImkz5Jx.js";import{a as E}from"./accounts-DUzbx6k8.js";import{r as c}from"./VForm-D5iPGkde.js";import{V as w}from"./VTextField-XoGTj1KG.js";import{a as g,V as R}from"./VRow-ozg66L7j.js";import{V as S}from"./VSwitch-DwxdeAEq.js";import{V as q}from"./VAlert-BuaaYN2h.js";import{V as A}from"./VAvatar-Cmga0vj6.js";const F={class:"m-label"},N={class:"text-white mr-4"},H={__name:"AccountPasswordSubForm",props:{modelValue:{type:Object,default:null},withPasswordCheck:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},editing:{type:Boolean,default:!1},formErrors:{type:Object,required:!1,default:()=>{}}},setup(s){const{$gettext:t}=P(),k=_(),r=s,e=b(()=>r.modelValue),f=b(()=>!r.disabled&&(!r.editing||e.value.password)),x=i=>i===e.value.new_password||t("Password mismatch");function C(){navigator.clipboard.writeText(e.value.new_password).then(()=>{k.displayNotification({msg:t("Password copied to clipboard")})})}function B(i){i?E.getRandomPassword().then(a=>{e.value.new_password=a.data.password}):(e.value.new_password=null,e.value.password_confirmation=null)}return(i,a)=>(n(),m("div",null,[s.withPasswordCheck?(n(),m(V,{key:0},[v("label",F,y(l(t)("Current password")),1),o(w,{modelValue:e.value.password,"onUpdate:modelValue":a[0]||(a[0]=d=>e.value.password=d),autocomplete:"new-password",variant:"outlined",type:"password",density:"compact","validate-on":"submit",rules:s.disabled?[]:[l(c).required],disabled:s.disabled,"error-messages":r.formErrors?r.formErrors.password:[]},null,8,["modelValue","rules","disabled","error-messages"])],64)):p("",!0),o(R,{justify:"center"},{default:u(()=>[o(g,{cols:"auto"},{default:u(()=>[o(S,{modelValue:e.value.random_password,"onUpdate:modelValue":[a[1]||(a[1]=d=>e.value.random_password=d),B],label:l(t)("Random password"),density:"compact",color:"primary",class:"text-center",disabled:s.disabled},null,8,["modelValue","label","disabled"])]),_:1}),o(g,null,{default:u(()=>[e.value.random_password?(n(),h(q,{key:0,style:{"background-color":"#515d78"},class:"ml-6",density:"compact"},{default:u(()=>[v("span",N,y(e.value.new_password),1),o(A,{size:"small",color:"white",variant:"text",density:"compact",disabled:s.disabled,icon:"mdi-clipboard-multiple-outline",title:l(t)("Copy to clipboard"),onClick:C},null,8,["disabled","title"])]),_:1})):p("",!0)]),_:1})]),_:1}),e.value.random_password?p("",!0):(n(),m(V,{key:1},[o(w,{modelValue:e.value.new_password,"onUpdate:modelValue":a[2]||(a[2]=d=>e.value.new_password=d),variant:"outlined",label:l(t)("Password"),type:"password",autocomplete:"new-password",density:"compact",disabled:s.disabled,rules:f.value?[l(c).required]:[]},null,8,["modelValue","label","disabled","rules"]),o(w,{modelValue:e.value.password_confirmation,"onUpdate:modelValue":a[3]||(a[3]=d=>e.value.password_confirmation=d),variant:"outlined",type:"password",label:l(t)("Confirmation"),density:"compact",disabled:s.disabled,rules:f.value?[l(c).required,x]:[],"error-messages":r.formErrors?r.formErrors.new_password:[]},null,8,["modelValue","label","disabled","rules","error-messages"])],64))]))}};export{H as _};