modoboa 2.4.10__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 (331) hide show
  1. modoboa/admin/api/v1/serializers.py +1 -0
  2. modoboa/admin/api/v2/serializers.py +15 -1
  3. modoboa/admin/api/v2/tests.py +11 -1
  4. modoboa/admin/lib.py +1 -1
  5. modoboa/admin/models/mailbox.py +15 -13
  6. modoboa/admin/models/mxrecord.py +4 -0
  7. modoboa/admin/tests/test_import_.py +11 -11
  8. modoboa/amavis/__init__.py +3 -0
  9. modoboa/amavis/app_settings.py +276 -0
  10. modoboa/amavis/apps.py +18 -0
  11. modoboa/amavis/checks/__init__.py +2 -0
  12. modoboa/amavis/checks/settings_checks.py +59 -0
  13. modoboa/amavis/dbrouter.py +35 -0
  14. modoboa/amavis/factories.py +164 -0
  15. modoboa/amavis/handlers.py +146 -0
  16. modoboa/amavis/lib.py +381 -0
  17. modoboa/amavis/management/__init__.py +0 -0
  18. modoboa/amavis/management/commands/__init__.py +0 -0
  19. modoboa/amavis/management/commands/amnotify.py +99 -0
  20. modoboa/amavis/management/commands/qcleanup.py +84 -0
  21. modoboa/amavis/migrations/0001_initial.py +340 -0
  22. modoboa/amavis/migrations/__init__.py +0 -0
  23. modoboa/amavis/models.py +226 -0
  24. modoboa/amavis/serializers.py +139 -0
  25. modoboa/amavis/sql_connector.py +240 -0
  26. modoboa/amavis/sql_email.py +66 -0
  27. modoboa/amavis/tasks.py +33 -0
  28. modoboa/amavis/templates/amavis/notifications/pending_requests.html +16 -0
  29. modoboa/amavis/tests/__init__.py +0 -0
  30. modoboa/amavis/tests/sa-learn +3 -0
  31. modoboa/amavis/tests/sample_messages/quarantined-input.txt +80 -0
  32. modoboa/amavis/tests/sample_messages/quarantined-output-plain_nolinks.txt +17 -0
  33. modoboa/amavis/tests/spamc +3 -0
  34. modoboa/amavis/tests/test_checks.py +25 -0
  35. modoboa/amavis/tests/test_handlers.py +214 -0
  36. modoboa/amavis/tests/test_lib.py +90 -0
  37. modoboa/amavis/tests/test_management_commands.py +45 -0
  38. modoboa/amavis/tests/test_sql_email.py +67 -0
  39. modoboa/amavis/tests/test_utils.py +19 -0
  40. modoboa/amavis/tests/test_viewsets.py +319 -0
  41. modoboa/amavis/urls.py +11 -0
  42. modoboa/amavis/utils.py +105 -0
  43. modoboa/amavis/viewsets.py +265 -0
  44. modoboa/core/api/v1/serializers.py +7 -5
  45. modoboa/core/api/v2/serializers.py +13 -2
  46. modoboa/core/api/v2/tests.py +34 -4
  47. modoboa/core/api/v2/urls.py +10 -5
  48. modoboa/core/api/v2/views.py +23 -2
  49. modoboa/core/api/v2/viewsets.py +24 -3
  50. modoboa/core/app_settings.py +11 -0
  51. modoboa/core/fido2_auth.py +1 -2
  52. modoboa/core/handlers.py +6 -2
  53. modoboa/core/management/commands/add_allowed_hosts.py +33 -0
  54. modoboa/core/management/commands/cleanlogs.py +9 -0
  55. modoboa/core/management/commands/load_initial_data.py +10 -0
  56. modoboa/core/migrations/0025_rename_user_email_is_active_core_user_email_c0c03f_idx.py +23 -5
  57. modoboa/core/tests/test_core.py +29 -0
  58. modoboa/core/utils.py +6 -0
  59. modoboa/dnstools/api/v2/serializers.py +9 -11
  60. modoboa/frontend_dist/assets/AccountAliasForm-BuSy_1n9.js +1 -0
  61. modoboa/frontend_dist/assets/AccountEditView-qdJmLM_e.js +1 -0
  62. modoboa/frontend_dist/assets/AccountLayout-DrN7vHsX.js +1 -0
  63. modoboa/frontend_dist/assets/AccountPasswordSubForm-DZGt_Xgq.js +1 -0
  64. modoboa/frontend_dist/assets/AccountView-CO65y0vZ.js +1 -0
  65. modoboa/frontend_dist/assets/AddressBook-BZNUlhek.js +1 -0
  66. modoboa/frontend_dist/assets/AdminLayout-CTNhuwTw.js +1 -0
  67. modoboa/frontend_dist/assets/AlarmsView-9yKGbmkC.css +1 -0
  68. modoboa/frontend_dist/assets/AlarmsView-DN_JIw9g.js +1 -0
  69. modoboa/frontend_dist/assets/AliasEditView-DjpPUTp9.js +1 -0
  70. modoboa/frontend_dist/assets/{AliasRecipientForm-DVZXWaUX.js → AliasRecipientForm-B1Y8wFdP.js} +1 -1
  71. modoboa/frontend_dist/assets/AliasView-GOJ5lyQH.js +1 -0
  72. modoboa/frontend_dist/assets/AuditTrailView-fbXmq70e.js +1 -0
  73. modoboa/frontend_dist/assets/CalendarView-LlQQNEPL.js +1 -0
  74. modoboa/frontend_dist/assets/{ChoiceField-7eU7c_rI.js → ChoiceField-B3ReQHVe.js} +1 -1
  75. modoboa/frontend_dist/assets/ComposeEmailForm-Bs1fZXAL.js +1 -0
  76. modoboa/frontend_dist/assets/ComposeEmailView-s3LMl3pO.js +1 -0
  77. modoboa/frontend_dist/assets/ConfirmDialog-DY_kUHLG.js +1 -0
  78. modoboa/frontend_dist/assets/{ConnectedLayout-BaJZ3BeR.css → ConnectedLayout-Bxh21hcH.css} +1 -1
  79. modoboa/frontend_dist/assets/ConnectedLayout-UWjiYBNw.js +1 -0
  80. modoboa/frontend_dist/assets/CreationForm-ORg3fazt.js +1 -0
  81. modoboa/frontend_dist/assets/DashboardView-Dplk9itS.js +1 -0
  82. modoboa/frontend_dist/assets/{DashboardView-BLlMi6Qb.css → DashboardView-gwwVAPvt.css} +1 -1
  83. modoboa/frontend_dist/assets/DomainAdminList-DVn9x0rB.js +1 -0
  84. modoboa/frontend_dist/assets/DomainEditView-nAoL64D_.js +1 -0
  85. modoboa/frontend_dist/assets/{DomainTransportForm-DPnPGBOp.js → DomainTransportForm-CA-DNUxX.js} +1 -1
  86. modoboa/frontend_dist/assets/{DomainView-BDKoBFYr.css → DomainView-CCLYXPHx.css} +1 -1
  87. modoboa/frontend_dist/assets/DomainView-CdXPpwJG.js +5 -0
  88. modoboa/frontend_dist/assets/DomainsView-B_59gowf.js +1 -0
  89. modoboa/frontend_dist/assets/DomainsView-DZ-ss9bI.css +1 -0
  90. modoboa/frontend_dist/assets/EmailField-CwcwI5xW.js +1 -0
  91. modoboa/frontend_dist/assets/EmailView-BshxcfAK.js +1 -0
  92. modoboa/frontend_dist/assets/EmptyLayout-DFfhnhLi.js +1 -0
  93. modoboa/frontend_dist/assets/FiltersView-Cf20MSTK.js +1 -0
  94. modoboa/frontend_dist/assets/ForwardEmailView-CZG062os.js +1 -0
  95. modoboa/frontend_dist/assets/{HtmlEditor-uM4AtIGi.js → HtmlEditor-Bh4c689R.js} +1 -1
  96. modoboa/frontend_dist/assets/IdentitiesView-BXAuU1YX.js +1 -0
  97. modoboa/frontend_dist/assets/{IdentitiesView-jmuItyMZ.css → IdentitiesView-DPrrRMS5.css} +1 -1
  98. modoboa/frontend_dist/assets/InformationView-C9vvvQhJ.css +1 -0
  99. modoboa/frontend_dist/assets/InformationView-Cn5FZW7H.js +1 -0
  100. modoboa/frontend_dist/assets/{LoadingData-CVD2Aen8.js → LoadingData-CdVvm4FI.js} +1 -1
  101. modoboa/frontend_dist/assets/{LoginCallbackView-sWzBke1g.js → LoginCallbackView-B9hAH4MI.js} +1 -1
  102. modoboa/frontend_dist/assets/{LoginView-wmN73W0f.js → LoginView-tHIR4Adc.js} +1 -1
  103. modoboa/frontend_dist/assets/MailboxView-Bugu2vhg.js +1 -0
  104. modoboa/frontend_dist/assets/MenuItems-PXjiG-fs.js +1 -0
  105. modoboa/frontend_dist/assets/MessageView-Cy4STShm.js +1 -0
  106. modoboa/frontend_dist/assets/MessagesView-DdkuEgfX.js +1 -0
  107. modoboa/frontend_dist/assets/MigrationsView-CidSEjCF.js +1 -0
  108. modoboa/frontend_dist/assets/{ParametersForm-BCeQljir.js → ParametersForm-CAv4SH-E.js} +1 -1
  109. modoboa/frontend_dist/assets/ParametersView-CX7Ffemw.js +1 -0
  110. modoboa/frontend_dist/assets/ParametersView-CrbNcmV3.js +1 -0
  111. modoboa/frontend_dist/assets/ProviderEditView-CrltAQXl.js +1 -0
  112. modoboa/frontend_dist/assets/ProviderGeneralForm-BYAzVnXM.js +1 -0
  113. modoboa/frontend_dist/assets/ProvidersView-osjIY4Ex.js +1 -0
  114. modoboa/frontend_dist/assets/QuarantineLayout-B8EcU9vS.js +1 -0
  115. modoboa/frontend_dist/assets/QuarantineView-D4gOE4EQ.css +1 -0
  116. modoboa/frontend_dist/assets/QuarantineView-D8Qg0MXA.js +1 -0
  117. modoboa/frontend_dist/assets/ReplyEmailView-BABPqWhd.js +1 -0
  118. modoboa/frontend_dist/assets/ResourcesForm-OaqdRYVs.js +1 -0
  119. modoboa/frontend_dist/assets/SelfServiceLayout-d277YTGR.js +1 -0
  120. modoboa/frontend_dist/assets/SettingsView-9iNcDhkI.js +6 -0
  121. modoboa/frontend_dist/assets/StatisticsView-cHsPyGkL.js +1 -0
  122. modoboa/frontend_dist/assets/TimeSerieChart--V83dcJ9.js +1 -0
  123. modoboa/frontend_dist/assets/UserLayout-B3sBiTcZ.js +1 -0
  124. modoboa/frontend_dist/assets/VAlert-BuaaYN2h.js +1 -0
  125. modoboa/frontend_dist/assets/VApp-CKP-6zGP.js +1 -0
  126. modoboa/frontend_dist/assets/VAutocomplete-Dwv6_Rzq.js +1 -0
  127. modoboa/frontend_dist/assets/VAvatar-Cmga0vj6.js +1 -0
  128. modoboa/frontend_dist/assets/VBadge-BQrRJ9S0.css +1 -0
  129. modoboa/frontend_dist/assets/VBadge-CixeK87a.js +1 -0
  130. modoboa/frontend_dist/assets/VCard-CxH9DWoK.js +1 -0
  131. modoboa/frontend_dist/assets/VCheckbox-62GOpvvP.js +1 -0
  132. modoboa/frontend_dist/assets/{VCheckboxBtn-j7di4leN.js → VCheckboxBtn-DMoNtKT8.js} +1 -1
  133. modoboa/frontend_dist/assets/VChip-D_styETR.js +1 -0
  134. modoboa/frontend_dist/assets/VColorPicker-BHscBGQV.js +1 -0
  135. modoboa/frontend_dist/assets/VContainer-B46caNs1.js +1 -0
  136. modoboa/frontend_dist/assets/VDataTable-Bh8NbVSx.js +1 -0
  137. modoboa/frontend_dist/assets/VDataTableServer-BDR5hOmo.js +1 -0
  138. modoboa/frontend_dist/assets/VDataTableVirtual-BOQlNtIG.js +1 -0
  139. modoboa/frontend_dist/assets/{VDialog-CZqM2Ofu.js → VDialog-BcTg7w6P.js} +1 -1
  140. modoboa/frontend_dist/assets/VExpansionPanels-BmH5Jl2Z.js +1 -0
  141. modoboa/frontend_dist/assets/VFileInput-BC4yAygd.js +1 -0
  142. modoboa/frontend_dist/assets/VForm-D5iPGkde.js +1 -0
  143. modoboa/frontend_dist/assets/VInput-CcxkaOXT.css +1 -0
  144. modoboa/frontend_dist/assets/VInput-CoDJzvaW.js +1 -0
  145. modoboa/frontend_dist/assets/VMenu-gUG70-zD.js +1 -0
  146. modoboa/frontend_dist/assets/VPicker-BXuKT3zB.js +1 -0
  147. modoboa/frontend_dist/assets/VProgressCircular-BtOPiGCg.js +1 -0
  148. modoboa/frontend_dist/assets/VRadioGroup-DIFZKSn-.js +1 -0
  149. modoboa/frontend_dist/assets/{VRow-C_Ydf6yr.js → VRow-ozg66L7j.js} +1 -1
  150. modoboa/frontend_dist/assets/VSelect-C3RjAa45.js +1 -0
  151. modoboa/frontend_dist/assets/VSelectionControl-zyz-fJvC.js +1 -0
  152. modoboa/frontend_dist/assets/VSheet-BNx2X4Mk.js +1 -0
  153. modoboa/frontend_dist/assets/VSpacer-DinPiXs9.js +1 -0
  154. modoboa/frontend_dist/assets/VSwitch-DwxdeAEq.js +1 -0
  155. modoboa/frontend_dist/assets/VTable-DaLxa4FO.js +1 -0
  156. modoboa/frontend_dist/assets/VTabs-BP0Hgsgm.js +1 -0
  157. modoboa/frontend_dist/assets/VTextField-BzBVKKob.css +1 -0
  158. modoboa/frontend_dist/assets/VTextField-XoGTj1KG.js +1 -0
  159. modoboa/frontend_dist/assets/VTextarea-wBlRMIv_.js +1 -0
  160. modoboa/frontend_dist/assets/VToolbar-CFZfqeOr.js +1 -0
  161. modoboa/frontend_dist/assets/VWindowItem-BB7ETW3b.js +1 -0
  162. modoboa/frontend_dist/assets/WebmailLayout-_Hk1XhVq.js +1 -0
  163. modoboa/frontend_dist/assets/accounts-DUzbx6k8.js +1 -0
  164. modoboa/frontend_dist/assets/admin-DewTk2H8.js +1 -0
  165. modoboa/frontend_dist/assets/{aliases-c3n-dCV_.js → aliases-4sXmjwXp.js} +1 -1
  166. modoboa/frontend_dist/assets/amavis-CC0li7_T.js +1 -0
  167. modoboa/frontend_dist/assets/amavis-DK8SHE6o.js +1 -0
  168. modoboa/frontend_dist/assets/{contacts-Bqckz8sr.js → contacts-BjghrPqZ.js} +1 -1
  169. modoboa/frontend_dist/assets/{domains-nBMR-fRf.js → domains-BSawReeu.js} +1 -1
  170. modoboa/frontend_dist/assets/{domains.store-ChZgLcqP.js → domains.store-D-vWCEIK.js} +1 -1
  171. modoboa/frontend_dist/assets/{filter-D7NrAf6a.js → filter-C82FUCw_.js} +1 -1
  172. modoboa/frontend_dist/assets/forwardRefs-cvcnlhoK.js +1 -0
  173. modoboa/frontend_dist/assets/global.store-DbkcI5o2.js +1 -0
  174. modoboa/frontend_dist/assets/{importExport-Dn9vYw7T.js → importExport-DzoL4Mvc.js} +1 -1
  175. modoboa/frontend_dist/assets/index-BImkz5Jx.js +984 -0
  176. modoboa/frontend_dist/assets/index-DuzUMVLM.js +1 -0
  177. modoboa/frontend_dist/assets/layout-C5FyYCHK.js +1 -0
  178. modoboa/frontend_dist/assets/{layout.store-BxBoBlgf.js → layout.store-NXWtFIwL.js} +1 -1
  179. modoboa/frontend_dist/assets/logos-BswdveCV.js +1 -0
  180. modoboa/frontend_dist/assets/{logs-DrTzylW7.js → logs-6CbtfaZS.js} +1 -1
  181. modoboa/frontend_dist/assets/{parameters-Lgiqp7aw.js → parameters-aSQiR7kN.js} +1 -1
  182. modoboa/frontend_dist/assets/{parameters.store-C9k9DuTj.js → parameters.store-CzQqVatx.js} +1 -1
  183. modoboa/frontend_dist/assets/permissions-DNoefz-n.js +1 -0
  184. modoboa/frontend_dist/assets/{ssrBoot-BsxW6uW4.js → ssrBoot-CKUX4kcb.js} +1 -1
  185. modoboa/frontend_dist/assets/{tag-CFK9dzMJ.js → tag-B_yWNNJD.js} +1 -1
  186. modoboa/frontend_dist/assets/transports-BDNB9wR5.js +1 -0
  187. modoboa/frontend_dist/assets/{webmail-KrD8ZhNM.js → webmail-CdU6CD9b.js} +1 -1
  188. modoboa/frontend_dist/index.html +1 -1
  189. modoboa/lib/email_utils.py +2 -2
  190. modoboa/lib/permissions.py +7 -0
  191. modoboa/lib/redis.py +1 -5
  192. modoboa/lib/test_runners.py +29 -0
  193. modoboa/lib/tests/__init__.py +5 -1
  194. modoboa/locale/br/LC_MESSAGES/django.po +87 -75
  195. modoboa/locale/cs/LC_MESSAGES/django.po +82 -74
  196. modoboa/locale/cs_CZ/LC_MESSAGES/django.po +145 -121
  197. modoboa/locale/de/LC_MESSAGES/django.mo +0 -0
  198. modoboa/locale/de/LC_MESSAGES/django.po +339 -651
  199. modoboa/locale/de_DE/LC_MESSAGES/django.po +87 -75
  200. modoboa/locale/el_GR/LC_MESSAGES/django.po +160 -135
  201. modoboa/locale/en/LC_MESSAGES/django.po +82 -74
  202. modoboa/locale/es/LC_MESSAGES/django.po +158 -131
  203. modoboa/locale/es_MX/LC_MESSAGES/django.po +82 -74
  204. modoboa/locale/fi/LC_MESSAGES/django.po +87 -75
  205. modoboa/locale/fr/LC_MESSAGES/django.mo +0 -0
  206. modoboa/locale/fr/LC_MESSAGES/django.po +469 -201
  207. modoboa/locale/hu/LC_MESSAGES/django.po +82 -74
  208. modoboa/locale/it/LC_MESSAGES/django.mo +0 -0
  209. modoboa/locale/it/LC_MESSAGES/django.po +148 -122
  210. modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
  211. modoboa/locale/ja_JP/LC_MESSAGES/django.po +201 -334
  212. modoboa/locale/ka/LC_MESSAGES/django.po +82 -74
  213. modoboa/locale/nl_NL/LC_MESSAGES/django.po +160 -132
  214. modoboa/locale/no/LC_MESSAGES/django.po +82 -74
  215. modoboa/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  216. modoboa/locale/pl_PL/LC_MESSAGES/django.po +172 -149
  217. modoboa/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  218. modoboa/locale/pt_BR/LC_MESSAGES/django.po +172 -144
  219. modoboa/locale/pt_PT/LC_MESSAGES/django.po +135 -112
  220. modoboa/locale/ro_RO/LC_MESSAGES/django.po +87 -75
  221. modoboa/locale/ru/LC_MESSAGES/django.po +142 -118
  222. modoboa/locale/si/LC_MESSAGES/django.po +82 -74
  223. modoboa/locale/sk/LC_MESSAGES/django.po +82 -74
  224. modoboa/locale/sk_SK/LC_MESSAGES/django.po +84 -76
  225. modoboa/locale/sl_SI/LC_MESSAGES/django.po +90 -76
  226. modoboa/locale/sv/LC_MESSAGES/django.mo +0 -0
  227. modoboa/locale/sv/LC_MESSAGES/django.po +172 -139
  228. modoboa/locale/tr/LC_MESSAGES/django.po +87 -75
  229. modoboa/locale/tr_TR/LC_MESSAGES/django.po +84 -74
  230. modoboa/locale/uk/LC_MESSAGES/django.po +82 -74
  231. modoboa/locale/zh/LC_MESSAGES/django.po +82 -74
  232. modoboa/locale/zh_CN/LC_MESSAGES/django.po +82 -74
  233. modoboa/locale/zh_TW/LC_MESSAGES/django.po +87 -75
  234. modoboa/parameters/api/v2/tests.py +2 -2
  235. modoboa/parameters/api/v2/viewsets.py +2 -0
  236. modoboa/policyd/tests.py +2 -0
  237. modoboa/urls_api_v2.py +6 -0
  238. {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/METADATA +6 -4
  239. {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/RECORD +244 -193
  240. modoboa/frontend_dist/assets/AccountAliasForm-DVXatAhB.js +0 -1
  241. modoboa/frontend_dist/assets/AccountEditView-DmvQjxpx.js +0 -1
  242. modoboa/frontend_dist/assets/AccountLayout-OGtZvlHR.js +0 -1
  243. modoboa/frontend_dist/assets/AccountPasswordSubForm-g3IEGrgM.js +0 -1
  244. modoboa/frontend_dist/assets/AccountView-DsxYqr3k.js +0 -1
  245. modoboa/frontend_dist/assets/AddressBook-3RoKiKon.js +0 -1
  246. modoboa/frontend_dist/assets/AdminLayout-CWfn8zaQ.js +0 -1
  247. modoboa/frontend_dist/assets/AlarmsView-Bheey-gp.css +0 -1
  248. modoboa/frontend_dist/assets/AlarmsView-D3Mh8ntf.js +0 -1
  249. modoboa/frontend_dist/assets/AliasEditView-C15eUZ11.js +0 -1
  250. modoboa/frontend_dist/assets/AliasView-Cyvc5vMb.js +0 -1
  251. modoboa/frontend_dist/assets/AuditTrailView-LI2XuLLn.js +0 -1
  252. modoboa/frontend_dist/assets/CalendarView-BpOlPh3f.js +0 -1
  253. modoboa/frontend_dist/assets/ComposeEmailForm-CNfI7ept.js +0 -1
  254. modoboa/frontend_dist/assets/ComposeEmailView-B866Xsrc.js +0 -1
  255. modoboa/frontend_dist/assets/ConfirmDialog-BvqxQsD1.js +0 -1
  256. modoboa/frontend_dist/assets/ConnectedLayout-BQ3ug6Jh.js +0 -1
  257. modoboa/frontend_dist/assets/CreationForm-C9Kh05ax.js +0 -1
  258. modoboa/frontend_dist/assets/DashboardView-Cw-gIcuB.js +0 -1
  259. modoboa/frontend_dist/assets/DomainAdminList-CsWUNKVk.js +0 -1
  260. modoboa/frontend_dist/assets/DomainEditView-DocxeOeW.js +0 -1
  261. modoboa/frontend_dist/assets/DomainView-Djc_0PsF.js +0 -5
  262. modoboa/frontend_dist/assets/DomainsView-B-Lxru7P.js +0 -1
  263. modoboa/frontend_dist/assets/DomainsView-DasJ0NdZ.css +0 -1
  264. modoboa/frontend_dist/assets/EmailField-BEKxuYni.js +0 -1
  265. modoboa/frontend_dist/assets/EmailView-BsR1Wes5.js +0 -1
  266. modoboa/frontend_dist/assets/EmptyLayout-BzPFOeLU.js +0 -1
  267. modoboa/frontend_dist/assets/FiltersView-5rmpC5cC.js +0 -1
  268. modoboa/frontend_dist/assets/ForwardEmailView-D7MbetcT.js +0 -1
  269. modoboa/frontend_dist/assets/IdentitiesView-BqjD9Lue.js +0 -1
  270. modoboa/frontend_dist/assets/InformationView-CghcvPn2.js +0 -1
  271. modoboa/frontend_dist/assets/InformationView-U5Ww-Sx1.css +0 -1
  272. modoboa/frontend_dist/assets/MailboxView-T_p-_ZtJ.js +0 -1
  273. modoboa/frontend_dist/assets/MenuItems-kHCMzR5E.js +0 -1
  274. modoboa/frontend_dist/assets/MessagesView-Cerv3xsy.js +0 -1
  275. modoboa/frontend_dist/assets/MigrationsView-7kjqPyYU.js +0 -1
  276. modoboa/frontend_dist/assets/ParametersView-DspBxVMV.js +0 -1
  277. modoboa/frontend_dist/assets/ParametersView-Dy0H5ep1.js +0 -1
  278. modoboa/frontend_dist/assets/ProviderEditView-DDLMOylC.js +0 -1
  279. modoboa/frontend_dist/assets/ProviderGeneralForm-BwOSKNHK.js +0 -1
  280. modoboa/frontend_dist/assets/ProvidersView-C99UD0WB.js +0 -1
  281. modoboa/frontend_dist/assets/ReplyEmailView-DAPBHldd.js +0 -1
  282. modoboa/frontend_dist/assets/ResourcesForm-D87PHeH0.js +0 -1
  283. modoboa/frontend_dist/assets/SettingsView-OxDo9wNd.js +0 -6
  284. modoboa/frontend_dist/assets/StatisticsView-CM__Eqku.js +0 -1
  285. modoboa/frontend_dist/assets/TimeSerieChart-C6j0uYR_.js +0 -1
  286. modoboa/frontend_dist/assets/UserLayout-CckCGnPS.js +0 -1
  287. modoboa/frontend_dist/assets/VAlert-B7mzOJIO.js +0 -1
  288. modoboa/frontend_dist/assets/VApp-BKxnjOoi.js +0 -1
  289. modoboa/frontend_dist/assets/VAutocomplete-Cc4_tcl1.js +0 -1
  290. modoboa/frontend_dist/assets/VAvatar-BAgTUIvX.js +0 -1
  291. modoboa/frontend_dist/assets/VCard-DFWiFORP.js +0 -1
  292. modoboa/frontend_dist/assets/VCheckbox-CKsH_vq3.js +0 -1
  293. modoboa/frontend_dist/assets/VChip-nZ0uhY7t.js +0 -1
  294. modoboa/frontend_dist/assets/VColorPicker-Os2aeP6J.js +0 -1
  295. modoboa/frontend_dist/assets/VContainer-Btam4lk2.js +0 -1
  296. modoboa/frontend_dist/assets/VDataTable-D_0_xJTl.js +0 -1
  297. modoboa/frontend_dist/assets/VDataTableServer-BWTt4Mzi.js +0 -1
  298. modoboa/frontend_dist/assets/VDataTableVirtual-BwVmkt4u.js +0 -1
  299. modoboa/frontend_dist/assets/VExpansionPanels-B5D6GOa3.js +0 -1
  300. modoboa/frontend_dist/assets/VFileInput-Cv9DIPki.js +0 -1
  301. modoboa/frontend_dist/assets/VForm-CpoZf60D.js +0 -1
  302. modoboa/frontend_dist/assets/VMenu-B_dVqOmo.js +0 -1
  303. modoboa/frontend_dist/assets/VPicker-CXkIGEze.js +0 -1
  304. modoboa/frontend_dist/assets/VProgressCircular-CrEXxs7k.js +0 -1
  305. modoboa/frontend_dist/assets/VRadioGroup-D8ypjYOO.js +0 -1
  306. modoboa/frontend_dist/assets/VSelect-CS51PDEt.js +0 -1
  307. modoboa/frontend_dist/assets/VSelectionControl-DiOqtY38.js +0 -1
  308. modoboa/frontend_dist/assets/VSheet-5VVWtHvs.js +0 -1
  309. modoboa/frontend_dist/assets/VSpacer-C6PZ3X24.js +0 -1
  310. modoboa/frontend_dist/assets/VSwitch-Chg5o-Cp.js +0 -1
  311. modoboa/frontend_dist/assets/VTable-KsiZ3cBz.js +0 -1
  312. modoboa/frontend_dist/assets/VTabs-tNrJIYO0.js +0 -1
  313. modoboa/frontend_dist/assets/VTextField-C-J20yj_.css +0 -1
  314. modoboa/frontend_dist/assets/VTextField-CLwRV0Cb.js +0 -1
  315. modoboa/frontend_dist/assets/VTextarea-Di8jbl8m.js +0 -1
  316. modoboa/frontend_dist/assets/VToolbar-CGwhgdmI.js +0 -1
  317. modoboa/frontend_dist/assets/VWindowItem-wWSFAGI-.js +0 -1
  318. modoboa/frontend_dist/assets/WebmailLayout-DsYThBrz.js +0 -1
  319. modoboa/frontend_dist/assets/admin-CJVLMHh9.js +0 -1
  320. modoboa/frontend_dist/assets/forwardRefs-Cpc3YYl6.js +0 -1
  321. modoboa/frontend_dist/assets/global.store-DUP26-A5.js +0 -1
  322. modoboa/frontend_dist/assets/index-DV9Li2cg.js +0 -852
  323. modoboa/frontend_dist/assets/index-DzL89N4E.js +0 -1
  324. modoboa/frontend_dist/assets/layout-CHO37cA6.js +0 -1
  325. modoboa/frontend_dist/assets/permissions-mL5hLHcW.js +0 -1
  326. modoboa/frontend_dist/assets/transports-TO08iTXJ.js +0 -1
  327. {modoboa-2.4.10.data → modoboa-2.5.0.data}/scripts/modoboa-admin.py +0 -0
  328. {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/WHEEL +0 -0
  329. {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/entry_points.txt +0 -0
  330. {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/licenses/LICENSE +0 -0
  331. {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,33 @@
1
+ """Async tasks."""
2
+
3
+ # from django.utils.translation import ngettext
4
+
5
+ from modoboa.core import models as core_models
6
+
7
+ from .lib import SpamassassinClient
8
+ from .sql_connector import SQLconnector
9
+
10
+
11
+ def manual_learning(user_pk: int, mtype: str, selection: list[dict], recipient_db: str):
12
+ """Task to trigger manual learning for given selection."""
13
+ user = core_models.User.objects.get(pk=user_pk)
14
+ connector = SQLconnector()
15
+ saclient = SpamassassinClient(user, recipient_db)
16
+ for item in selection:
17
+ content = connector.get_mail_content(item["mailid"].encode("ascii"))
18
+ result = (
19
+ saclient.learn_spam(item["rcpt"], content)
20
+ if mtype == "spam"
21
+ else saclient.learn_ham(item["rcpt"], content)
22
+ )
23
+ if not result:
24
+ break
25
+ connector.set_msgrcpt_status(item["rcpt"], item["mailid"], mtype[0].upper())
26
+ if saclient.error is None:
27
+ saclient.done()
28
+
29
+ # message = ngettext("%(count)d message processed successfully",
30
+ # "%(count)d messages processed successfully",
31
+ # len(selection)) % {"count": len(selection)}
32
+ # else:
33
+ # message = saclient.error
@@ -0,0 +1,16 @@
1
+ {% load i18n %}
2
+
3
+ {% blocktranslate trimmed count counter=total %}
4
+ {{ counter }} release request is pending for action.
5
+ {% plural %}
6
+ {{ counter }} release requests are pending for action.
7
+ {% endblocktranslate %}
8
+ {% translate "Sketch:" %}
9
+ {% for msg in requests %}
10
+ {% translate "From:" %} {{ msg.mail.sid.email }}
11
+ {% translate "To:" %} {{ msg.rid.email }}
12
+ {% translate "Date:" %} {{ msg.mail.time_datetime|date:"DATETIME_FORMAT" }}
13
+ {% translate "Subject:" %} {{ msg.mail.subject }}
14
+ {% translate "Act on this message:" %} {{ baseurl }}/user/quarantine/{{ msg.mail.mail_id }}/{{ msg.rid.email }}
15
+ {% endfor %}
16
+ {% blocktranslate %}Please visit {{ listingurl }} for a full listing.{% endblocktranslate %}
File without changes
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ exit 0
@@ -0,0 +1,80 @@
1
+ X-Envelope-From: <it-edu@evil.example.net>
2
+ X-Envelope-To: <me@example.net>
3
+ X-Envelope-To-Blocked: <me@example.net>
4
+ X-Quarantine-ID: <nZkKQ_MUuyil>
5
+ X-Amavis-Alert: BAD HEADER SECTION, Non-encoded non-ASCII data (and not UTF-8)
6
+ (char 85 hex): Subject: I think I saw you in my dreams\x{85}
7
+ X-Spam-Flag: YES
8
+ X-Spam-Score: 8.178
9
+ X-Spam-Level: ********
10
+ X-Spam-Status: Yes, score=8.178 tag=-999 tag2=3.9 kill=5
11
+ tests=[HEADER_FROM_DIFFERENT_DOMAINS=0.25, HTML_IMAGE_ONLY_28=0.726,
12
+ HTML_MESSAGE=0.001, MIME_HTML_ONLY=1.105, RCVD_IN_MSPIKE_H2=-0.001,
13
+ TVD_PH_BODY_ACCOUNTS_PRE=0.001, T_REMOTE_IMAGE=0.01,
14
+ T_RP_MATCHES_RCVD=-0.01, URIBL_BLACK=1.7, URIBL_DBL_ABUSE_SPAM=2,
15
+ URI_WPADMIN=2.396] autolearn=no autolearn_force=no
16
+ Received: from mail.example.net ([127.0.0.1])
17
+ by mail.example.net (mail.example.net [127.0.0.1]) (amavisd-new, port 10024)
18
+ with LMTP id nZkKQ_MUuyil for <me@example.net>;
19
+ Sat, 16 Dec 2017 13:09:49 +0000 (GMT)
20
+ Received: from evil.example.net (mail.evil.example.net [10.0.0.1])
21
+ by mail.example.net (Postfix) with ESMTPS id 62B2E1AF12D
22
+ for <me@example.net>; Sat, 16 Dec 2017 13:09:49 +0000 (GMT)
23
+ Received: from it-edu by evil.example.net with local (Exim 4.82)
24
+ (envelope-from <it-edu@evil.example.net>)
25
+ id 1eQCDr-0005NE-IT
26
+ for me@example.net; Sat, 16 Dec 2017 16:09:47 +0300
27
+ To: me@example.net
28
+ Subject: =?UTF-8?Q?Account_Suspension_Notification_=21?=
29
+ X-PHP-Originating-Script: 520:maill.php
30
+ Date: Sat, 16 Dec 2017 16:09:47 +0300
31
+ From: Notice <Notice@fake.example.net>
32
+ Reply-To: Notice@fake.example.net
33
+ Message-ID: <6619a5295cefcf484a58944fc280eb90@evil.example.net>
34
+ X-Priority: 1
35
+ X-Mailer: PHPMailer 5.2.10 (https://github.com/PHPMailer/PHPMailer/)
36
+ MIME-Version: 1.0
37
+ Content-Type: text/html; charset=iso-8859-1
38
+ Content-Transfer-Encoding: 8bit
39
+
40
+ <!DOCTYPE html>
41
+ <html>
42
+ <HEAD><STYLE>
43
+ .cssage P
44
+ {
45
+ margin:0px;
46
+ padding:0px
47
+ }
48
+ body.cssage
49
+ {
50
+ font-size: 12pt;
51
+ font-family:Calibri
52
+ }
53
+ </STYLE>
54
+
55
+ </HEAD>
56
+ <BODY class="cssage">
57
+ <DIV dir="ltr"><FONT size=2 face="Arial, Helvetica, sans-serif"></FONT><BR>
58
+ <DIV>
59
+ <DIV>
60
+ <TABLE style="FONT-FAMILY: 'Times New Roman'" border=0 cellSpacing=0 cellPadding=0 width=600 align="center">
61
+ <TR class=header>
62
+ <TD vAlign=bottom><SPAN style="FONT-FAMILY: 'Trebuchet MS', Arial, Helvetica, sans-serif; COLOR: rgb(159,159,159); FONT-SIZE: 12px" >This is an automated email, please do not reply</SPAN></TD>
63
+ <TD align=right><IMG style="WIDTH: 215px; HEIGHT: 66px" alt="" src="http://im55.gulfup.com/oLrUdE.jpg"></TD></TR>
64
+ <TR >
65
+ <TD height=14 vAlign=bottom colSpan=2><IMG src="https://www.payza.com/emails/images/bar.png" width="100%" height=3></TD></TR>
66
+ <TR>
67
+ <TD colSpan=2>
68
+ <H1 style="PADDING-BOTTOM: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; FONT-FAMILY: 'Trebuchet MS', Arial, Helvetica, sans-serif; COLOR: rgb(189,151,0); FONT-SIZE: 24px; FONT-WEIGHT: normal; PADDING-TOP: 10px">Dear Client</H1>
69
+ <P style="LINE-HEIGHT: 21px; FONT-FAMILY: 'Trebuchet MS', Arial, Helvetica, sans-serif; COLOR: rgb(84,84,84); FONT-SIZE: 14px">We've noticed that some of your account information appears to be missing or incorrect
70
+ We need to verify your account information in order to continue using your Apple ID, Please Verify your account information by clicking on the link below<BR><BR><B><A style="COLOR: rgb(27,144,234)" href="http://chiikulab.com/wp-admin/images/Update/Apple2018%20V2.1/
71
+ " target=_blank>Click here to Verify your ID</A></B></P></TD></TR>
72
+ <TR>
73
+ <TD colSpan=2>
74
+ <DIV><SPAN style="FONT-FAMILY: 'Trebuchet MS', Arial, Helvetica, sans-serif; COLOR: rgb(51,102,204); FONT-SIZE: 15px" ></SPAN><BR></DIV><br><SPAN style="FONT-FAMILY: 'Trebuchet MS', Arial, Helvetica, sans-serif; COLOR: rgb(202,162,0); FONT-SIZE: 17px" class=ecxthanks>Thanks for choosing Apple,</SPAN><BR style="COLOR: rgb(202,162,0)"><SPAN style="FONT-FAMILY: 'Trebuchet MS', Arial, Helvetica, sans-serif; COLOR: rgb(202,162,0); FONT-SIZE: 15px" >Apple Team</SPAN></TD></TR>
75
+ <TR >
76
+ <TD height=14 vAlign=bottom colSpan=2><IMG src="https://www.payza.com/emails/images/bar.png" width="100%" height=3></TD></TR>
77
+ <TR>
78
+ <TD colSpan=2>
79
+ <P style="PADDING-BOTTOM: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; FONT-FAMILY: 'Trebuchet MS', Arial, Helvetica, sans-serif; COLOR: rgb(84,84,84); FONT-SIZE: 12px; PADDING-TOP: 10px" >&copy; 2017 Apple. All rights reserved.</P>
80
+ <P style="FONT-FAMILY: 'Trebuchet MS', Arial, Helvetica, sans-serif; COLOR: rgb(186,148,0); FONT-SIZE: 11px">Email ID: 163327</P></TD></TR></TABLE></DIV></DIV></DIV></BODY></html>
@@ -0,0 +1,17 @@
1
+ <pre>This is an automated email, please do not reply
2
+
3
+ # Dear Client
4
+
5
+ We&#x27;ve noticed that some of your account information appears to be missing or
6
+ incorrect We need to verify your account information in order to continue
7
+ using your Apple ID, Please Verify your account information by clicking on the
8
+ link below
9
+
10
+ **Click here to Verify your ID**
11
+
12
+ Thanks for choosing Apple,
13
+ Apple Team
14
+
15
+ © 2017 Apple. All rights reserved.
16
+
17
+ Email ID: 163327</pre>
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ exit 5
@@ -0,0 +1,25 @@
1
+ from django.test import TestCase
2
+ from django.test.utils import override_settings
3
+
4
+ from ..checks import settings_checks
5
+
6
+
7
+ class CheckSessionCookieSecureTest(TestCase):
8
+
9
+ databases = "__all__"
10
+
11
+ @override_settings(AMAVIS_DEFAULT_DATABASE_ENCODING="LATIN-1")
12
+ def test_amavis_database_encoding_incorrect(self):
13
+ """
14
+ If AMAVIS_DEFAULT_DATABASE_ENCODING is incorrect provide one warning.
15
+ """
16
+ self.assertEqual(
17
+ settings_checks.check_amavis_database_encoding(None), [settings_checks.W001]
18
+ )
19
+
20
+ @override_settings(AMAVIS_DEFAULT_DATABASE_ENCODING="UTF-8")
21
+ def test_amavis_database_encoding_correct(self):
22
+ """
23
+ If AMAVIS_DEFAULT_DATABASE_ENCODING is correct, there's no warning.
24
+ """
25
+ self.assertEqual(settings_checks.check_amavis_database_encoding(None), [])
@@ -0,0 +1,214 @@
1
+ """Amavis tests."""
2
+
3
+ import os
4
+
5
+ from django.test import override_settings
6
+ from django.urls import reverse
7
+
8
+ from modoboa.admin import factories as admin_factories, models as admin_models
9
+ from modoboa.core import models as core_models
10
+ from modoboa.lib.tests import ModoAPITestCase
11
+ from modoboa.transport import factories as tr_factories
12
+ from .. import factories, lib, models
13
+
14
+
15
+ class DomainTestCase(ModoAPITestCase):
16
+ """Check that database is populated."""
17
+
18
+ databases = "__all__"
19
+
20
+ def setUp(self):
21
+ """Initiate test context."""
22
+ self.admin = core_models.User.objects.get(username="admin")
23
+
24
+ def test_create_domain(self):
25
+ """Test domain creation."""
26
+ domain = admin_factories.DomainFactory(name="domain.test")
27
+ name = f"@{domain.name}"
28
+ policy = models.Policy.objects.get(policy_name=name)
29
+ user = models.Users.objects.filter(policy=policy).first()
30
+ self.assertIsNot(user, None)
31
+ self.assertEqual(user.email, name)
32
+
33
+ # Create a domain alias
34
+ self.client.force_authenticate(self.admin)
35
+ url = reverse("v2:domain_alias-list")
36
+ data = {"name": "dalias.test", "target": domain.pk}
37
+ resp = self.client.post(url, data, format="json")
38
+ self.assertEqual(resp.status_code, 201)
39
+ name = "@dalias.test"
40
+ self.assertFalse(models.Policy.objects.filter(policy_name=name).exists())
41
+ user = models.Users.objects.get(email=name)
42
+ self.assertEqual(user.policy, policy)
43
+
44
+ # Delete domain alias
45
+ url = reverse("v2:domain_alias-detail", args=[resp.json()["pk"]])
46
+ resp = self.client.delete(url)
47
+ self.assertEqual(resp.status_code, 204)
48
+ self.assertFalse(models.Users.objects.filter(email=name).exists())
49
+
50
+ def test_rename_domain(self):
51
+ """Test domain rename."""
52
+ domain = admin_factories.DomainFactory(name="domain.test")
53
+ domain.name = "domain1.test"
54
+ domain.save()
55
+ name = f"@{domain.name}"
56
+ self.assertTrue(models.Users.objects.filter(email=name).exists())
57
+ self.assertTrue(models.Policy.objects.filter(policy_name=name).exists())
58
+
59
+ # Now from form
60
+ self.client.force_authenticate(self.admin)
61
+ rdomain = admin_factories.DomainFactory(name="domain.relay", type="relaydomain")
62
+ rdomain.transport = tr_factories.TransportFactory(
63
+ pattern=rdomain.name,
64
+ service="relay",
65
+ _settings={
66
+ "relay_target_host": "external.host.tld",
67
+ "relay_target_port": "25",
68
+ "relay_verify_recipients": False,
69
+ },
70
+ )
71
+ rdomain.save()
72
+ url = reverse("v2:domain-detail", args=[rdomain.pk])
73
+ values = {
74
+ "name": "domain2.relay",
75
+ "quota": rdomain.quota,
76
+ "default_mailbox_quota": rdomain.default_mailbox_quota,
77
+ "type": "relaydomain",
78
+ "enabled": rdomain.enabled,
79
+ "transport": {
80
+ "service": rdomain.transport.service,
81
+ "settings": {
82
+ "relay_target_host": "127.0.0.1",
83
+ "relay_target_port": 25,
84
+ },
85
+ },
86
+ }
87
+ resp = self.client.put(url, values, format="json")
88
+ self.assertTrue(resp.status_code, 200)
89
+
90
+ def test_delete_domain(self):
91
+ """Test domain removal."""
92
+ domain = admin_factories.DomainFactory(name="domain.test")
93
+ domain.delete(None)
94
+ name = f"@{domain.name}"
95
+ self.assertFalse(models.Users.objects.filter(email=name).exists())
96
+ self.assertFalse(models.Policy.objects.filter(policy_name=name).exists())
97
+
98
+ def test_update_domain_policy(self):
99
+ """Check domain policy edition."""
100
+ self.client.force_authenticate(self.admin)
101
+ domain = admin_factories.DomainFactory(name="domain.test")
102
+ url = reverse("v2:amavis-policy-detail", args=[domain.pk])
103
+ custom_title = "This is SPAM!"
104
+ data = {"bypass_virus_checks": "Y", "spam_subject_tag2": custom_title}
105
+ resp = self.client.patch(url, data, format="json")
106
+ self.assertEqual(resp.status_code, 200)
107
+ name = f"@{domain.name}"
108
+ policy = models.Policy.objects.get(policy_name=name)
109
+ self.assertEqual(policy.spam_subject_tag2, custom_title)
110
+
111
+
112
+ @override_settings(SA_LOOKUP_PATH=(os.path.dirname(__file__),))
113
+ class ManualLearningTestCase(ModoAPITestCase):
114
+ """Check manual learning mode."""
115
+
116
+ databases = "__all__"
117
+
118
+ @classmethod
119
+ def setUpTestData(cls): # NOQA:N802
120
+ """Create test data."""
121
+ super().setUpTestData()
122
+ admin_factories.populate_database()
123
+
124
+ def test_alias_creation(self):
125
+ """Check alias creation."""
126
+ self.set_global_parameter("user_level_learning", True)
127
+
128
+ # Fake activation because we don't have test data yet for
129
+ # amavis...
130
+ lib.setup_manual_learning_for_mbox(
131
+ admin_models.Mailbox.objects.get(address="user", domain__name="test.com")
132
+ )
133
+ lib.setup_manual_learning_for_mbox(
134
+ admin_models.Mailbox.objects.get(address="admin", domain__name="test.com")
135
+ )
136
+
137
+ url = reverse("v2:alias-list")
138
+ values = {
139
+ "address": "alias1000@test.com",
140
+ "recipients": ["admin@test.com"],
141
+ "enabled": True,
142
+ }
143
+ resp = self.client.post(url, values, format="json")
144
+ self.assertEqual(resp.status_code, 201)
145
+ policy = models.Policy.objects.get(policy_name=values["recipients"][0])
146
+ user = models.Users.objects.get(email=values["address"])
147
+ self.assertEqual(user.policy, policy)
148
+
149
+ values = {
150
+ "address": "user@test.com",
151
+ "recipients": ["admin@test.com"],
152
+ "enabled": True,
153
+ }
154
+ resp = self.client.post(url, values, format="json")
155
+ self.assertEqual(resp.status_code, 201)
156
+ policy = models.Policy.objects.get(policy_name=values["recipients"][0])
157
+ user = models.Users.objects.get(email=values["address"])
158
+ self.assertEqual(user.policy, policy)
159
+
160
+ def test_mailbox_rename(self):
161
+ """Check rename case."""
162
+ self.set_global_parameter("user_level_learning", True)
163
+
164
+ lib.setup_manual_learning_for_mbox(
165
+ admin_models.Mailbox.objects.get(address="user", domain__name="test.com")
166
+ )
167
+
168
+ user = core_models.User.objects.get(username="user@test.com")
169
+ url = reverse("v2:account-detail", args=[user.pk])
170
+ values = {
171
+ "username": "user2@test.com",
172
+ "role": "SimpleUsers",
173
+ "mailbox": {"use_domain_quota": True},
174
+ }
175
+ resp = self.client.put(url, values, format="json")
176
+ self.assertEqual(resp.status_code, 200)
177
+ self.assertTrue(models.Users.objects.filter(email=values["username"]).exists())
178
+
179
+ def test_learn_alias_spam_as_admin(self):
180
+ """Check learning spam for an alias address as admin user."""
181
+ user = core_models.User.objects.get(username="admin")
182
+ recipient_db = "user"
183
+ rcpt = "alias@test.com"
184
+ sender = "spam@evil.corp"
185
+ content = factories.SPAM_BODY.format(rcpt=rcpt, sender=sender)
186
+
187
+ saclient = lib.SpamassassinClient(user, recipient_db)
188
+ result = saclient.learn_spam(rcpt, content)
189
+ self.assertTrue(result)
190
+
191
+ def test_delete_catchall_alias(self):
192
+ """Check that Users record is not deleted."""
193
+ self.set_global_parameter("user_level_learning", True)
194
+
195
+ # Fake activation because we don't have test data yet for
196
+ # amavis...
197
+ lib.setup_manual_learning_for_mbox(
198
+ admin_models.Mailbox.objects.get(address="admin", domain__name="test.com")
199
+ )
200
+
201
+ url = reverse("v2:alias-list")
202
+ values = {
203
+ "address": "@test.com",
204
+ "recipients": ["admin@test.com"],
205
+ "enabled": True,
206
+ }
207
+ resp = self.client.post(url, values, format="json")
208
+ self.assertTrue(resp.status_code, 201)
209
+
210
+ alias = admin_models.Alias.objects.get(address="@test.com")
211
+ url = reverse("v2:alias-detail", args=[alias.pk])
212
+ resp = self.client.delete(url)
213
+ self.assertEqual(resp.status_code, 204)
214
+ self.assertTrue(models.Users.objects.get(email="@test.com"))
@@ -0,0 +1,90 @@
1
+ from django.test import SimpleTestCase
2
+
3
+ from modoboa.lib.tests import ModoTestCase
4
+ from modoboa.amavis.lib import cleanup_email_address, make_query_args
5
+
6
+
7
+ class MakeQueryArgsTests(ModoTestCase):
8
+ """Tests for modoboa_amavis.lib.make_query_args()."""
9
+
10
+ def test_simple_email_address_001(self):
11
+ """Check case insensitive email address without recipient delimiter."""
12
+ self.set_global_parameter("localpart_is_case_sensitive", False)
13
+ self.set_global_parameter("recipient_delimiter", "")
14
+ address = "User+Foo@sub.exAMPLE.COM"
15
+ expected_output = [
16
+ "user+foo@sub.example.com",
17
+ ]
18
+ output = make_query_args(address)
19
+ self.assertEqual(output, expected_output)
20
+
21
+ def test_simple_email_address_002(self):
22
+ """Check case sensitive email address with recipient delimiter."""
23
+ self.set_global_parameter("localpart_is_case_sensitive", True)
24
+ self.set_global_parameter("recipient_delimiter", "+")
25
+ address = "User+Foo@sub.exAMPLE.COM"
26
+ expected_output = [
27
+ "User+Foo@sub.exAMPLE.COM",
28
+ "User+Foo@sub.example.com",
29
+ "User@sub.example.com",
30
+ ]
31
+ output = make_query_args(address)
32
+ self.assertEqual(output, expected_output)
33
+
34
+ def test_simple_email_address_003(self):
35
+ """Check email address with recipient delimiter wildcard."""
36
+ self.set_global_parameter("localpart_is_case_sensitive", False)
37
+ self.set_global_parameter("recipient_delimiter", "+")
38
+ address = "User+Foo@sub.exAMPLE.COM"
39
+ expected_output = [
40
+ "user+foo@sub.example.com",
41
+ "user+.*@sub.example.com",
42
+ "user@sub.example.com",
43
+ ]
44
+ output = make_query_args(address, exact_extension=False, wildcard=".*")
45
+ self.assertEqual(output, expected_output)
46
+
47
+ def test_simple_email_address_004(self):
48
+ """Check domain search."""
49
+ self.set_global_parameter("localpart_is_case_sensitive", False)
50
+ self.set_global_parameter("recipient_delimiter", "+")
51
+ address = "User+Foo@sub.exAMPLE.COM"
52
+ expected_output = [
53
+ "user+foo@sub.example.com",
54
+ "user+.*@sub.example.com",
55
+ "user@sub.example.com",
56
+ "@sub.example.com",
57
+ "@.",
58
+ ]
59
+ output = make_query_args(
60
+ address, exact_extension=False, wildcard=".*", domain_search=True
61
+ )
62
+ self.assertEqual(output, expected_output)
63
+
64
+ def test_simple_email_address_idn(self):
65
+ """Check email address with international domain name."""
66
+ self.set_global_parameter("localpart_is_case_sensitive", False)
67
+ self.set_global_parameter("recipient_delimiter", "")
68
+ address = "Pingüino@Pájaro.Niño.exAMPLE.COM"
69
+ expected_output = [
70
+ "Pingüino@Pájaro.Niño.exAMPLE.COM",
71
+ "pingüino@xn--pjaro-xqa.xn--nio-8ma.example.com",
72
+ ]
73
+ output = make_query_args(address)
74
+ self.assertEqual(output, expected_output)
75
+
76
+
77
+ class FixUTF8EncodingTests(SimpleTestCase):
78
+ """Tests for modoboa_amavis.lib.cleanup_email_address()."""
79
+
80
+ def test_value_with_newline(self):
81
+ value = '"John Smith" <john.smith@example.com>\n'
82
+ expected_output = "John Smith <john.smith@example.com>"
83
+ output = cleanup_email_address(value)
84
+ self.assertEqual(output, expected_output)
85
+
86
+ def test_no_name(self):
87
+ value = "<john.smith@example.com>"
88
+ expected_output = "john.smith@example.com"
89
+ output = cleanup_email_address(value)
90
+ self.assertEqual(output, expected_output)
@@ -0,0 +1,45 @@
1
+ """Management commands tests."""
2
+
3
+ from dateutil.relativedelta import relativedelta
4
+
5
+ from django.core.management import call_command
6
+ from django.utils import timezone
7
+
8
+ from modoboa.lib.tests import ModoTestCase
9
+ from .. import factories, models
10
+
11
+
12
+ class ManagementCommandTestCase(ModoTestCase):
13
+ """Management commands tests."""
14
+
15
+ databases = "__all__"
16
+
17
+ def test_qcleanup(self):
18
+ """Test qcleanup command."""
19
+ factories.create_spam("user@test.com", rs="D")
20
+ call_command("qcleanup")
21
+ self.assertEqual(models.Quarantine.objects.count(), 0)
22
+ self.assertEqual(models.Msgs.objects.count(), 0)
23
+ self.assertEqual(models.Maddr.objects.count(), 0)
24
+ self.assertEqual(models.Msgrcpt.objects.count(), 0)
25
+
26
+ factories.create_spam("user@test.com", rs="D")
27
+ msgrcpt = factories.create_spam("user@test.com", rs="R")
28
+ call_command("qcleanup")
29
+ # Should not raise anything
30
+ msgrcpt.refresh_from_db()
31
+
32
+ self.set_global_parameter("released_msgs_cleanup", True)
33
+ call_command("qcleanup")
34
+ with self.assertRaises(models.Msgrcpt.DoesNotExist):
35
+ msgrcpt.refresh_from_db()
36
+
37
+ msgrcpt = factories.create_spam("user@test.com")
38
+ msgrcpt.mail.time_num = int(
39
+ (timezone.now() - relativedelta(days=40)).strftime("%s")
40
+ )
41
+ msgrcpt.mail.save(update_fields=["time_num"])
42
+ self.set_global_parameter("max_messages_age", 30)
43
+ call_command("qcleanup")
44
+ with self.assertRaises(models.Msgrcpt.DoesNotExist):
45
+ msgrcpt.refresh_from_db()
@@ -0,0 +1,67 @@
1
+ """Tests for sql_email."""
2
+
3
+ import os
4
+
5
+ from django.test import TestCase
6
+ from django.utils.encoding import smart_str
7
+
8
+ from ..sql_email import SQLemail
9
+
10
+ SAMPLES_DIR = os.path.realpath(
11
+ os.path.join(os.path.dirname(__file__), "sample_messages")
12
+ )
13
+
14
+
15
+ class EmailTestImplementation(SQLemail):
16
+
17
+ def _fetch_message(self):
18
+ message_path = os.path.join(SAMPLES_DIR, f"{self.mailid}-input.txt")
19
+ assert os.path.isfile(message_path), f"{message_path} does not exist."
20
+
21
+ with open(message_path, "rb") as fp:
22
+ mail_text = fp.read()
23
+
24
+ return mail_text
25
+
26
+
27
+ class EmailTests(TestCase):
28
+ """Tests for modoboa_amavis.sql_email.SQLEmail
29
+
30
+ When writing new sample messages use the following naming convention for
31
+ the sample files stored in sample_messages:
32
+
33
+ input: {message_id}-input.txt
34
+ output: {message_id}-output-{dformat}_{no,}links.txt
35
+ """
36
+
37
+ def _get_expected_output(self, message_id, **kwargs):
38
+ ext = kwargs.get("dformat", "plain")
39
+ ext += "_links" if "links" in kwargs and kwargs["links"] else "_nolinks"
40
+ message_path = os.path.join(SAMPLES_DIR, f"{message_id}-output-{ext}.txt")
41
+ assert os.path.isfile(message_path), f"{message_path} does not exist."
42
+
43
+ with open(message_path, "rb") as fp:
44
+ # output should always be unicode (py2) or str (py3)
45
+ mail_text = smart_str(fp.read())
46
+
47
+ return mail_text
48
+
49
+ def _test_email(self, message_id, **kwargs):
50
+ """Boiler plate code for testing e-mails."""
51
+ expected_output = self._get_expected_output(message_id, **kwargs)
52
+ output = EmailTestImplementation(message_id, **kwargs).body
53
+ self.assertEqual(output, expected_output)
54
+
55
+ def test_amavis_aleart_header(self):
56
+ email = EmailTestImplementation("quarantined")
57
+ self.assertEqual(email.qtype, "BAD HEADER SECTION")
58
+ self.assertEqual(
59
+ email.qreason,
60
+ "Non-encoded non-ASCII data (and not UTF-8) (char 85 "
61
+ "hex): Subject: I think I saw you in my dreams\\x{85}",
62
+ )
63
+
64
+ def test_email_multipart_with_no_text(self):
65
+ """for a multipart message without a text/plain part convert the
66
+ text/html to text/plain"""
67
+ self._test_email("quarantined")
@@ -0,0 +1,19 @@
1
+ from django.test import SimpleTestCase
2
+
3
+ from modoboa.amavis.utils import fix_utf8_encoding
4
+
5
+
6
+ class FixUTF8EncodingTests(SimpleTestCase):
7
+ """Tests for modoboa_amavis.utils.fix_utf8_encoding()."""
8
+
9
+ def test_4_byte_unicode(self):
10
+ value = "\xf0\x9f\x99\x88"
11
+ expected_output = "\U0001f648" # == See No Evil Moneky
12
+ output = fix_utf8_encoding(value)
13
+ self.assertEqual(output, expected_output)
14
+
15
+ def test_truncated_4_byte_unicode(self):
16
+ value = "\xf0\x9f\x99"
17
+ expected_output = "\xf0\x9f\x99"
18
+ output = fix_utf8_encoding(value)
19
+ self.assertEqual(output, expected_output)