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,319 @@
1
+ import os
2
+ from unittest import mock
3
+
4
+ from rq import SimpleWorker
5
+
6
+ from django.core import mail
7
+ from django.core.management import call_command
8
+ from django.test import override_settings
9
+ from django.urls import reverse
10
+
11
+ import django_rq
12
+
13
+ from modoboa.admin import factories as admin_factories, models as admin_models
14
+ from modoboa.amavis import factories, models
15
+ from modoboa.amavis.utils import smart_str
16
+ from modoboa.core import models as core_models
17
+ from modoboa.lib.tests import ModoAPITestCase
18
+
19
+
20
+ AMAVIS_SETTINGS = {
21
+ "localpart_is_case_sensitive": False,
22
+ "recipient_delimiter": "+",
23
+ "max_attachment_size": 14,
24
+ "released_msgs_cleanup": False,
25
+ "am_pdp_mode": "unix",
26
+ "am_pdp_host": "localhost",
27
+ "am_pdp_port": 9998,
28
+ "user_can_release": False,
29
+ "self_service": False,
30
+ "manual_learning": True,
31
+ "sa_is_local": True,
32
+ }
33
+
34
+
35
+ class TestDataMixin:
36
+ """A mixin to provide test data."""
37
+
38
+ @classmethod
39
+ def setUpTestData(cls): # NOQA:N802
40
+ """Create some content."""
41
+ super().setUpTestData()
42
+ cls.msgrcpt = factories.create_spam("user@test.com")
43
+
44
+
45
+ @override_settings(SA_LOOKUP_PATH=(os.path.dirname(__file__),))
46
+ class QuarantineViewSetTestCase(TestDataMixin, ModoAPITestCase):
47
+
48
+ databases = "__all__"
49
+
50
+ @classmethod
51
+ def setUpTestData(cls): # NOQA:N802
52
+ """Create test data."""
53
+ super().setUpTestData()
54
+ admin_factories.populate_database()
55
+
56
+ def tearDown(self):
57
+ """Restore msgrcpt state."""
58
+ self.msgrcpt.rs = " "
59
+ self.msgrcpt.save(update_fields=["rs"])
60
+ self.set_global_parameter("domain_level_learning", False)
61
+ self.set_global_parameter("user_level_learning", False)
62
+
63
+ def test_list(self):
64
+ url = reverse("v2:amavis-quarantine-list")
65
+ resp = self.client.get(url)
66
+ self.assertEqual(resp.status_code, 200)
67
+ content = resp.json()
68
+ self.assertEqual(content["count"], 1)
69
+
70
+ resp = self.client.get(f"{url}?search=pouet&criteria=both")
71
+ content = resp.json()
72
+ self.assertEqual(content["count"], 0)
73
+
74
+ resp = self.client.get(f"{url}?msgtype=V")
75
+ content = resp.json()
76
+ self.assertEqual(content["count"], 0)
77
+ factories.create_virus("user@test.com")
78
+ resp = self.client.get(f"{url}?msgtype=V")
79
+ content = resp.json()
80
+ self.assertEqual(content["count"], 1)
81
+
82
+ def test_retrieve(self):
83
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
84
+ rcpt = smart_str(self.msgrcpt.rid.email)
85
+ url = reverse("v2:amavis-quarantine-detail", args=[mail_id])
86
+ resp = self.client.get(f"{url}?rcpt={rcpt}")
87
+ self.assertEqual(resp.status_code, 200)
88
+
89
+ user = core_models.User.objects.get(username="user@test.com")
90
+ self.client.force_authenticate(user)
91
+ resp = self.client.get(f"{url}?rcpt={rcpt}")
92
+ self.assertEqual(resp.status_code, 200)
93
+ self.msgrcpt.refresh_from_db()
94
+ self.assertEqual(self.msgrcpt.rs, "V")
95
+
96
+ def test_headers(self):
97
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
98
+ url = reverse("v2:amavis-quarantine-headers", args=[mail_id])
99
+ resp = self.client.get(url)
100
+ self.assertEqual(resp.status_code, 200)
101
+
102
+ def test_headers_selfservice(self):
103
+ self.client.logout()
104
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
105
+ url = reverse("v2:amavis-quarantine-headers", args=[mail_id])
106
+ url = f"{url}?secret_id={smart_str(self.msgrcpt.mail.secret_id)}"
107
+ response = self.client.get(url)
108
+ self.assertEqual(response.status_code, 403)
109
+
110
+ self.set_global_parameter("self_service", True)
111
+ response = self.client.get(url)
112
+ self.assertEqual(response.status_code, 403)
113
+ url = f"{url}&rcpt={smart_str(self.msgrcpt.rid.email)}"
114
+ response = self.client.get(url)
115
+ self.assertEqual(response.status_code, 200)
116
+
117
+ def test_delete(self):
118
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
119
+ url = reverse("v2:amavis-quarantine-delete", args=[mail_id])
120
+ data = {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
121
+ response = self.client.post(url, data, format="json")
122
+ self.assertEqual(response.status_code, 204)
123
+ self.msgrcpt.refresh_from_db()
124
+ self.assertEqual(self.msgrcpt.rs, "D")
125
+
126
+ def test_delete_selfservice(self):
127
+ self.client.logout()
128
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
129
+ url = reverse("v2:amavis-quarantine-delete", args=[mail_id])
130
+ data = {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
131
+ response = self.client.post(url, data, format="json")
132
+ self.assertEqual(response.status_code, 403)
133
+ self.set_global_parameter("self_service", True)
134
+ data["secret_id"] = smart_str(self.msgrcpt.mail.secret_id)
135
+ response = self.client.post(url, data, format="json")
136
+ self.assertEqual(response.status_code, 204)
137
+ self.msgrcpt.refresh_from_db()
138
+ self.assertEqual(self.msgrcpt.rs, "D")
139
+
140
+ def test_delete_selection(self):
141
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
142
+ url = reverse("v2:amavis-quarantine-delete-selection")
143
+ data = {
144
+ "selection": [
145
+ {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
146
+ ]
147
+ }
148
+ response = self.client.post(url, data, format="json")
149
+ self.assertEqual(response.status_code, 204)
150
+ self.msgrcpt.refresh_from_db()
151
+ self.assertEqual(self.msgrcpt.rs, "D")
152
+
153
+ @mock.patch("socket.socket")
154
+ def test_release(self, mock_socket):
155
+ mock_socket.return_value.recv.return_value = b"250 1234 Ok\r\n"
156
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
157
+ url = reverse("v2:amavis-quarantine-release", args=[mail_id])
158
+ data = {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
159
+ response = self.client.post(url, data, format="json")
160
+ self.assertEqual(response.status_code, 200)
161
+ self.msgrcpt.refresh_from_db()
162
+ self.assertEqual(self.msgrcpt.rs, "R")
163
+
164
+ @mock.patch("socket.socket")
165
+ def test_release_selfservice(self, mock_socket):
166
+ """Test release view."""
167
+ mock_socket.return_value.recv.return_value = b"250 1234 Ok\r\n"
168
+ self.client.logout()
169
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
170
+ url = reverse("v2:amavis-quarantine-release", args=[mail_id])
171
+ self.set_global_parameter("self_service", True)
172
+ # Missing rcpt -> fails
173
+ data = {"mailid": mail_id, "secret_id": "1234"}
174
+ resp = self.client.post(url, data, format="json")
175
+ self.assertEqual(resp.status_code, 403)
176
+ # Wrong secret_id -> fails
177
+ data["rcpt"] = smart_str(self.msgrcpt.rid.email)
178
+ resp = self.client.post(url, data, format="json")
179
+ self.assertEqual(resp.status_code, 403)
180
+ # Request mode
181
+ data["secret_id"] = smart_str(self.msgrcpt.mail.secret_id)
182
+ resp = self.client.post(url, data, format="json")
183
+ self.assertEqual(resp.status_code, 200)
184
+ self.msgrcpt.refresh_from_db()
185
+ self.assertEqual(self.msgrcpt.rs, "p")
186
+ # Direct release mode
187
+ self.set_global_parameter("user_can_release", True)
188
+ resp = self.client.post(url, data, format="json")
189
+ self.assertEqual(resp.status_code, 200)
190
+ self.msgrcpt.refresh_from_db()
191
+ self.assertEqual(self.msgrcpt.rs, "R")
192
+
193
+ def test_release_request(self):
194
+ """Test release request mode."""
195
+ user = core_models.User.objects.get(username="user@test.com")
196
+ self.client.force_authenticate(user)
197
+
198
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
199
+ url = reverse("v2:amavis-quarantine-release", args=[mail_id])
200
+ data = {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
201
+ response = self.client.post(url, data, format="json")
202
+ self.assertEqual(response.status_code, 200)
203
+ self.msgrcpt.refresh_from_db()
204
+ self.assertEqual(self.msgrcpt.rs, "p")
205
+
206
+ # Send notification to admins
207
+ call_command("amnotify")
208
+ self.assertEqual(len(mail.outbox), 1)
209
+
210
+ def _test_mark_selection(self, action, status):
211
+ """Mark message common code."""
212
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
213
+ url = reverse("v2:amavis-quarantine-mark-selection")
214
+ data = {
215
+ "selection": [
216
+ {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
217
+ ],
218
+ "type": action,
219
+ }
220
+ response = self.client.post(url, data, format="json")
221
+ self.assertEqual(response.status_code, 204)
222
+ queue = django_rq.get_queue("modoboa")
223
+ worker = SimpleWorker([queue], connection=queue.connection)
224
+ worker.work(burst=True)
225
+ self.msgrcpt.refresh_from_db()
226
+ self.assertEqual(self.msgrcpt.rs, status)
227
+
228
+ self.msgrcpt.rs = " "
229
+ self.msgrcpt.save(update_fields=["rs"])
230
+ self.set_global_parameter("sa_is_local", False)
231
+ response = self.client.post(url, data, format="json")
232
+ self.assertEqual(response.status_code, 204)
233
+ worker.work(burst=True)
234
+ self.msgrcpt.refresh_from_db()
235
+ self.assertEqual(self.msgrcpt.rs, status)
236
+
237
+ def test_mark_as_ham(self):
238
+ """Test mark_as_ham view."""
239
+ self._test_mark_selection("ham", "H")
240
+
241
+ # Check domain level learning
242
+ self.set_global_parameter("domain_level_learning", True)
243
+ data = {
244
+ "selection": [
245
+ {
246
+ "rcpt": smart_str(self.msgrcpt.rid.email),
247
+ "mailid": smart_str(self.msgrcpt.mail.mail_id),
248
+ },
249
+ ],
250
+ "type": "ham",
251
+ "database": "domain",
252
+ }
253
+ url = reverse("v2:amavis-quarantine-mark-selection")
254
+ response = self.client.post(url, data, format="json")
255
+ self.assertEqual(response.status_code, 204)
256
+
257
+ # Check user level learning
258
+ self.set_global_parameter("user_level_learning", True)
259
+ data["database"] = "user"
260
+ response = self.client.post(url, data, format="json")
261
+ self.assertEqual(response.status_code, 204)
262
+
263
+ def test_mark_as_spam(self):
264
+ """Test mark_as_spam view."""
265
+ self._test_mark_selection("spam", "S")
266
+
267
+ def test_manual_learning_as_user(self):
268
+ """Test learning when connected as a simple user."""
269
+ user = core_models.User.objects.get(username="user@test.com")
270
+ self.client.force_authenticate(user)
271
+ mail_id = smart_str(self.msgrcpt.mail.mail_id)
272
+ data = {
273
+ "selection": [
274
+ {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
275
+ ],
276
+ "type": "ham",
277
+ }
278
+ url = reverse("v2:amavis-quarantine-mark-selection")
279
+ response = self.client.post(url, data, format="json")
280
+ self.assertEqual(response.status_code, 200)
281
+ self.assertEqual(response.json(), {"status": "ok"})
282
+ self.set_global_parameter("user_level_learning", True)
283
+ self._test_mark_selection("ham", "H")
284
+
285
+
286
+ class PolicyViewSetTestCase(ModoAPITestCase):
287
+
288
+ databases = "__all__"
289
+
290
+ @classmethod
291
+ def setUpTestData(cls): # NOQA:N802
292
+ """Create test data."""
293
+ super().setUpTestData()
294
+ admin_factories.populate_database()
295
+
296
+ def test_get_policy(self):
297
+ domain = admin_models.Domain.objects.get(name="test.com")
298
+ url = reverse("v2:amavis-policy-detail", args=[domain.pk])
299
+ resp = self.client.get(url)
300
+ self.assertEqual(resp.status_code, 200)
301
+
302
+ def test_update_policy(self):
303
+ domain = admin_models.Domain.objects.get(name="test.com")
304
+ data = {"bypass_virus_checks": "Y", "spam_tag2_level": 6}
305
+ url = reverse("v2:amavis-policy-detail", args=[domain.pk])
306
+ resp = self.client.patch(url, data, format="json")
307
+ self.assertEqual(resp.status_code, 200)
308
+ p = models.Policy.objects.get(users__email="@test.com")
309
+ self.assertEqual(p.bypass_virus_checks, data["bypass_virus_checks"])
310
+ self.assertEqual(p.spam_tag2_level, data["spam_tag2_level"])
311
+
312
+
313
+ class ParametersAPITestCase(ModoAPITestCase):
314
+
315
+ def test_update(self):
316
+ settings = AMAVIS_SETTINGS.copy()
317
+ url = reverse("v2:parameter-global-detail", args=["amavis"])
318
+ resp = self.client.put(url, settings, format="json")
319
+ self.assertEqual(resp.status_code, 200)
modoboa/amavis/urls.py ADDED
@@ -0,0 +1,11 @@
1
+ """Amavis urls."""
2
+
3
+ from rest_framework import routers
4
+
5
+ from modoboa.amavis import viewsets
6
+
7
+ router = routers.SimpleRouter()
8
+ router.register(r"quarantine", viewsets.QuarantineViewSet, basename="amavis-quarantine")
9
+ router.register(r"policies", viewsets.PolicyViewSet, basename="amavis-policy")
10
+
11
+ urlpatterns = router.urls
@@ -0,0 +1,105 @@
1
+ """A collection of utility functions for working with the Amavis database."""
2
+
3
+ import chardet
4
+
5
+ from django.conf import settings
6
+ from django.db.models.expressions import Func
7
+ from django.utils.encoding import (
8
+ smart_bytes as django_smart_bytes,
9
+ smart_str as django_smart_str,
10
+ )
11
+
12
+
13
+ """
14
+ Byte fields for text data are EVIL.
15
+
16
+ MySQL uses `varbyte` fields which mysqlclient client maps to `str` (Py2) or
17
+ `bytes` (Py3), Djangos smart_* functions work as expected.
18
+
19
+ PostgreSQL uses `bytea` fields which psycopg2 maps to `memoryview`,
20
+ Djangos smart_* functions don't work as expected, you must call `tobytes()` on
21
+ the memoryview for them to work.
22
+
23
+ For convenience use smart_bytes and smart_str from this file in modoboa.amavis
24
+ to avoid any headaches.
25
+ """
26
+
27
+
28
+ def smart_bytes(value, *args, **kwargs):
29
+ if isinstance(value, memoryview):
30
+ value = value.tobytes()
31
+ return django_smart_bytes(value, *args, **kwargs)
32
+
33
+
34
+ def smart_str(value, *args, **kwargs):
35
+ if isinstance(value, memoryview):
36
+ value = value.tobytes()
37
+ return django_smart_str(value, *args, **kwargs)
38
+
39
+
40
+ def fix_utf8_encoding(value):
41
+ """Fix utf-8 strings that contain utf-8 escaped characters.
42
+
43
+ msgs.from_addr and msgs.subject potentialy contain badly escaped utf-8
44
+ characters, this utility function fixes that and should be used anytime
45
+ these fields are accesses.
46
+
47
+ Didn't even know the raw_unicode_escape encoding existed :)
48
+ https://docs.python.org/3/library/codecs.html?highlight=raw_unicode_escape#python-specific-encodings
49
+ """
50
+ assert isinstance(value, str), "value should be of type str"
51
+
52
+ if len(value) == 0:
53
+ # short circuit for empty strings
54
+ return ""
55
+
56
+ bytes_value = value.encode("raw_unicode_escape")
57
+ try:
58
+ value = bytes_value.decode("utf-8")
59
+ except UnicodeDecodeError:
60
+ encoding = chardet.detect(bytes_value)
61
+ try:
62
+ value = bytes_value.decode(encoding["encoding"], "replace")
63
+ except (TypeError, UnicodeDecodeError):
64
+ # ??? use the original value, we've done our best to try and
65
+ # convert it to a clean utf-8 string.
66
+ pass
67
+
68
+ return value
69
+
70
+
71
+ class ConvertFrom(Func):
72
+ """Convert a binary value to a string.
73
+ Calls the database specific function to convert a binary value to a string
74
+ using the encoding set in AMAVIS_DEFAULT_DATABASE_ENCODING.
75
+ """
76
+
77
+ """PostgreSQL implementation.
78
+ See https://www.postgresql.org/docs/9.3/static/functions-string.html#FUNCTIONS-STRING-OTHER""" # NOQA:E501
79
+ function = "convert_from"
80
+ arity = 1
81
+ template = (
82
+ f"%(function)s(%(expressions)s, '{settings.AMAVIS_DEFAULT_DATABASE_ENCODING}')"
83
+ )
84
+
85
+ def as_mysql(self, compiler, connection):
86
+ """MySQL implementation.
87
+ See https://dev.mysql.com/doc/refman/5.5/en/cast-functions.html#function_convert
88
+ """ # NOQA:E501
89
+ return super().as_sql(
90
+ compiler,
91
+ connection,
92
+ function="CONVERT",
93
+ template=f"%(function)s(%(expressions)s USING {settings.AMAVIS_DEFAULT_DATABASE_ENCODING})",
94
+ arity=1,
95
+ )
96
+
97
+ def as_sqlite(self, compiler, connection):
98
+ """SQLite implementation.
99
+ SQLite has no equivilant function, just return the field."""
100
+ return super().as_sql(
101
+ compiler,
102
+ connection,
103
+ template="%(expressions)s",
104
+ arity=1,
105
+ )