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
@@ -398,6 +398,20 @@ class AccountMeSerializer(v1_serializers.AccountSerializer):
398
398
  return [f"{p.content_type.app_label}.{p.codename}" for p in permissions]
399
399
 
400
400
 
401
+ class AccountMeUpdateSerializer(serializers.ModelSerializer):
402
+ """Update serializer for connected user."""
403
+
404
+ class Meta:
405
+ model = core_models.User
406
+ fields = (
407
+ "first_name",
408
+ "last_name",
409
+ "language",
410
+ "phone_number",
411
+ "secondary_email",
412
+ )
413
+
414
+
401
415
  class AccountSerializer(v1_serializers.AccountSerializer):
402
416
  """Add support for user resources."""
403
417
 
@@ -0,0 +1,3 @@
1
+ """The amavis frontend of Modoboa."""
2
+
3
+ default_app_config = "modoboa.amavis.apps.AmavisConfig"
@@ -0,0 +1,276 @@
1
+ import collections
2
+
3
+ from django.utils.translation import gettext_lazy
4
+
5
+
6
+ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
7
+ [
8
+ (
9
+ "amavis",
10
+ {
11
+ "label": gettext_lazy("Amavis settings"),
12
+ "params": collections.OrderedDict(
13
+ [
14
+ (
15
+ "localpart_is_case_sensitive",
16
+ {
17
+ "label": gettext_lazy("Localpart is case sensitive"),
18
+ "help_text": gettext_lazy(
19
+ "Value should match amavisd.conf variable "
20
+ "$localpart_is_case_sensitive"
21
+ ),
22
+ },
23
+ ),
24
+ (
25
+ "recipient_delimiter",
26
+ {
27
+ "label": gettext_lazy("Recipient delimiter"),
28
+ "help_text": gettext_lazy(
29
+ "Value should match amavisd.conf variable "
30
+ "$recipient_delimiter"
31
+ ),
32
+ },
33
+ ),
34
+ ]
35
+ ),
36
+ },
37
+ ),
38
+ (
39
+ "quarantine",
40
+ {
41
+ "label": gettext_lazy("Quarantine settings"),
42
+ "params": collections.OrderedDict(
43
+ [
44
+ (
45
+ "max_messages_age",
46
+ {
47
+ "label": gettext_lazy("Maximum message age"),
48
+ "help_text": gettext_lazy(
49
+ "Quarantine messages maximum age (in days) before deletion"
50
+ ),
51
+ },
52
+ )
53
+ ]
54
+ ),
55
+ },
56
+ ),
57
+ (
58
+ "releasing",
59
+ {
60
+ "label": gettext_lazy("Message releasing"),
61
+ "params": collections.OrderedDict(
62
+ [
63
+ (
64
+ "released_msgs_cleanup",
65
+ {
66
+ "label": gettext_lazy("Remove released messages"),
67
+ "help_text": gettext_lazy(
68
+ "Remove messages marked as released while cleaning up "
69
+ "the database"
70
+ ),
71
+ },
72
+ ),
73
+ (
74
+ "am_pdp_mode",
75
+ {
76
+ "label": gettext_lazy("Amavis connection mode"),
77
+ "help_text": gettext_lazy(
78
+ "Mode used to access the PDP server"
79
+ ),
80
+ },
81
+ ),
82
+ (
83
+ "am_pdp_host",
84
+ {
85
+ "label": gettext_lazy("PDP server address"),
86
+ "help_text": gettext_lazy(
87
+ "PDP server address (if inet mode)"
88
+ ),
89
+ "display": "am_pdp_mode=inet",
90
+ },
91
+ ),
92
+ (
93
+ "am_pdp_port",
94
+ {
95
+ "label": gettext_lazy("PDP server port"),
96
+ "help_text": gettext_lazy(
97
+ "PDP server port (if inet mode)"
98
+ ),
99
+ "display": "am_pdp_mode=inet",
100
+ },
101
+ ),
102
+ (
103
+ "am_pdp_socket",
104
+ {
105
+ "label": gettext_lazy("PDP server socket"),
106
+ "help_text": gettext_lazy(
107
+ "Path to the PDP server socket (if unix mode)"
108
+ ),
109
+ "display": "am_pdp_mode=unix",
110
+ },
111
+ ),
112
+ (
113
+ "user_can_release",
114
+ {
115
+ "label": gettext_lazy("Allow direct release"),
116
+ "help_text": gettext_lazy(
117
+ "Allow users to directly release their messages"
118
+ ),
119
+ },
120
+ ),
121
+ (
122
+ "self_service",
123
+ {
124
+ "label": gettext_lazy("Enable self-service mode"),
125
+ "help_text": gettext_lazy(
126
+ "Activate the 'self-service' mode"
127
+ ),
128
+ },
129
+ ),
130
+ (
131
+ "notifications_sender",
132
+ {
133
+ "label": gettext_lazy("Notifications sender"),
134
+ "help_text": gettext_lazy(
135
+ "The e-mail address used to send notitications"
136
+ ),
137
+ },
138
+ ),
139
+ ]
140
+ ),
141
+ },
142
+ ),
143
+ (
144
+ "learning",
145
+ {
146
+ "label": gettext_lazy("Manual learning"),
147
+ "params": collections.OrderedDict(
148
+ [
149
+ (
150
+ "manual_learning",
151
+ {
152
+ "label": gettext_lazy("Enable manual learning"),
153
+ "help_text": gettext_lazy(
154
+ "Allow super administrators to manually train Spamassassin"
155
+ ),
156
+ },
157
+ ),
158
+ (
159
+ "sa_is_local",
160
+ {
161
+ "label": gettext_lazy("Is Spamassassin local?"),
162
+ "help_text": gettext_lazy(
163
+ "Tell if Spamassassin is running on the same server than modoboa"
164
+ ),
165
+ "display": "manual_learning=true",
166
+ },
167
+ ),
168
+ (
169
+ "default_user",
170
+ {
171
+ "label": gettext_lazy("Default user"),
172
+ "help_text": gettext_lazy(
173
+ "Name of the user owning the default bayesian database"
174
+ ),
175
+ "display": "manual_learning=true",
176
+ },
177
+ ),
178
+ (
179
+ "spamd_address",
180
+ {
181
+ "label": gettext_lazy("Spamd address"),
182
+ "help_text": gettext_lazy(
183
+ "The IP address where spamd can be reached"
184
+ ),
185
+ "display": "sa_is_local=false",
186
+ },
187
+ ),
188
+ (
189
+ "spamd_port",
190
+ {
191
+ "label": gettext_lazy("Spamd port"),
192
+ "help_text": gettext_lazy(
193
+ "The TCP port spamd is listening on"
194
+ ),
195
+ "display": "sa_is_local=false",
196
+ },
197
+ ),
198
+ (
199
+ "domain_level_learning",
200
+ {
201
+ "label": gettext_lazy(
202
+ "Enable per-domain manual learning"
203
+ ),
204
+ "help_text": gettext_lazy(
205
+ "Allow domain administrators to train Spamassassin "
206
+ "(within dedicated per-domain databases)"
207
+ ),
208
+ "display": "manual_learning=true",
209
+ },
210
+ ),
211
+ (
212
+ "user_level_learning",
213
+ {
214
+ "label": gettext_lazy(
215
+ "Enable per-user manual learning"
216
+ ),
217
+ "help_text": gettext_lazy(
218
+ "Allow simple users to personally train Spamassassin "
219
+ "(within a dedicated database)"
220
+ ),
221
+ "display": "manual_learning=true",
222
+ },
223
+ ),
224
+ ]
225
+ ),
226
+ },
227
+ ),
228
+ ]
229
+ )
230
+
231
+ USER_PREFERENCES_STRUCT = collections.OrderedDict(
232
+ [
233
+ (
234
+ "display",
235
+ {
236
+ "label": gettext_lazy("Display"),
237
+ "params": collections.OrderedDict(
238
+ [
239
+ (
240
+ "messages_per_page",
241
+ {
242
+ "label": gettext_lazy(
243
+ "Number of displayed emails per page"
244
+ ),
245
+ "help_text": gettext_lazy(
246
+ "Set the maximum number of messages displayed in a page"
247
+ ),
248
+ },
249
+ )
250
+ ]
251
+ ),
252
+ },
253
+ )
254
+ ]
255
+ )
256
+
257
+
258
+ def load_settings():
259
+ """Load amavis settings."""
260
+ from modoboa.parameters import tools as param_tools
261
+ from modoboa.amavis import serializers
262
+
263
+ param_tools.registry.add(
264
+ "global",
265
+ "amavis",
266
+ gettext_lazy("Amavis"),
267
+ GLOBAL_PARAMETERS_STRUCT,
268
+ serializers.GlobalParametersSerializer,
269
+ )
270
+ param_tools.registry.add(
271
+ "user",
272
+ "amavis",
273
+ gettext_lazy("Amavis"),
274
+ USER_PREFERENCES_STRUCT,
275
+ serializers.UserPreferencesSerializer,
276
+ )
modoboa/amavis/apps.py ADDED
@@ -0,0 +1,18 @@
1
+ """AppConfig for amavis."""
2
+
3
+ from django.apps import AppConfig
4
+
5
+
6
+ class AmavisConfig(AppConfig):
7
+ """App configuration."""
8
+
9
+ name = "modoboa.amavis"
10
+ verbose_name = "Modoboa amavis frontend"
11
+
12
+ def ready(self):
13
+ # Import these to force registration of checks and signals
14
+ from . import checks # NOQA:F401
15
+ from . import handlers # NOQA:F401
16
+ from modoboa.amavis.app_settings import load_settings
17
+
18
+ load_settings()
@@ -0,0 +1,2 @@
1
+ # Import these to force registration of checks
2
+ from . import settings_checks # NOQA:F401
@@ -0,0 +1,59 @@
1
+ from django.conf import settings
2
+ from django.core.checks import Warning, register
3
+ from django.db import connections
4
+ from django.utils.translation import gettext as _
5
+
6
+ W001 = Warning(
7
+ _(
8
+ "AMAVIS_DEFAULT_DATABASE_ENCODING does not match the character "
9
+ "encoding used by the Amavis database."
10
+ ),
11
+ hint=_(
12
+ "Check your database character encoding and set/update "
13
+ "AMAVIS_DEFAULT_DATABASE_ENCODING."
14
+ ),
15
+ id="modoboa-amavis.W001",
16
+ )
17
+
18
+ W002 = Warning(
19
+ _("Modoboa Amavis has not been tested using the selected database engine."),
20
+ hint=_("Try using PostgreSQL, MySQL or MariaDB."),
21
+ id="modoboa-amavis.W002",
22
+ )
23
+
24
+
25
+ @register(deploy=True)
26
+ def check_amavis_database_encoding(app_configs, **kwargs):
27
+ """Ensure AMAVIS_DEFAULT_DATABASE_ENCODING is set to the correct value."""
28
+ errors = []
29
+ db_engine = settings.DATABASES["amavis"]["ENGINE"]
30
+ sql_query = None
31
+ if "postgresql" in db_engine:
32
+ sql_query = (
33
+ "SELECT pg_encoding_to_char(encoding) "
34
+ "FROM pg_database "
35
+ "WHERE datname = %s;"
36
+ )
37
+ elif "mysql" in db_engine:
38
+ sql_query = (
39
+ "SELECT DEFAULT_CHARACTER_SET_NAME "
40
+ "FROM INFORMATION_SCHEMA.SCHEMATA "
41
+ "WHERE SCHEMA_NAME = %s;"
42
+ )
43
+ elif "sqlite" in db_engine:
44
+ sql_query = "PRAGMA encoding;"
45
+ else:
46
+ errors.append(W002)
47
+ return errors
48
+
49
+ with connections["amavis"].cursor() as cursor:
50
+ if "sqlite" in db_engine:
51
+ cursor.execute(sql_query)
52
+ else:
53
+ cursor.execute(sql_query, [settings.DATABASES["amavis"]["NAME"]])
54
+ encoding = cursor.fetchone()[0]
55
+
56
+ if encoding.lower() != settings.AMAVIS_DEFAULT_DATABASE_ENCODING.lower():
57
+ errors.append(W001)
58
+
59
+ return errors
@@ -0,0 +1,35 @@
1
+ class AmavisRouter:
2
+ """A router to control all database operations on models in
3
+ the amavis application"""
4
+
5
+ def db_for_read(self, model, **hints):
6
+ """Point all operations on amavis models to 'amavis'."""
7
+ if model._meta.app_label == "amavis":
8
+ return "amavis"
9
+ return None
10
+
11
+ def db_for_write(self, model, **hints):
12
+ """Point all operations on amavis models to 'amavis'."""
13
+ if model._meta.app_label == "amavis":
14
+ return "amavis"
15
+ return None
16
+
17
+ def allow_relation(self, obj1, obj2, **hints):
18
+ """Allow any relation if a model in amavis is involved."""
19
+ if obj1._meta.app_label == "amavis" or obj2._meta.app_label == "amavis":
20
+ return True
21
+ return None
22
+
23
+ def allow_migrate(self, db, app_label, model=None, **hints):
24
+ """
25
+ Make sure the auth app only appears in the 'amavis'
26
+ database.
27
+ """
28
+ if app_label == "amavis":
29
+ # modoboa_amavis migrations should be created in the amavis
30
+ # database.
31
+ return db == "amavis"
32
+ elif db == "amavis":
33
+ # Don't create non modoboa_amavis migrations in the amavis database.
34
+ return False
35
+ return None
@@ -0,0 +1,164 @@
1
+ """Amavis factories."""
2
+
3
+ import datetime
4
+ import time
5
+
6
+ import factory
7
+
8
+ from . import models
9
+ from .utils import smart_bytes
10
+
11
+ SPAM_BODY = """X-Envelope-To: <{rcpt}>
12
+ X-Envelope-To-Blocked: <{rcpt}>
13
+ X-Quarantine-ID: <nq6ekd4wtXZg>
14
+ X-Spam-Flag: YES
15
+ X-Spam-Score: 1000.985
16
+ X-Spam-Level: ****************************************************************
17
+ X-Spam-Status: Yes, score=1000.985 tag=2 tag2=6.31 kill=6.31
18
+ tests=[ALL_TRUSTED=-1, GTUBE=1000, PYZOR_CHECK=1.985]
19
+ autolearn=no autolearn_force=no
20
+ Received: from demo.modoboa.org ([127.0.0.1])
21
+ by localhost (demo.modoboa.org [127.0.0.1]) (amavisd-new, port 10024)
22
+ with ESMTP id nq6ekd4wtXZg for <user@demo.local>;
23
+ Thu, 9 Nov 2017 15:59:52 +0100 (CET)
24
+ Received: from demo.modoboa.org (localhost [127.0.0.1])
25
+ by demo.modoboa.org (Postfix) with ESMTP
26
+ for <user@demo.local>; Thu, 9 Nov 2017 15:59:52 +0100 (CET)
27
+ Content-Type: text/plain; charset="utf-8"
28
+ MIME-Version: 1.0
29
+ Content-Transfer-Encoding: quoted-printable
30
+ Subject: Sample message
31
+ From: {sender}
32
+ To: {rcpt}
33
+ Message-ID: <151023959268.5550.5713670714483771838@demo.modoboa.org>
34
+ Date: Thu, 09 Nov 2017 15:59:52 +0100
35
+
36
+ This is the GTUBE, the
37
+ Generic
38
+ Test for
39
+ Unsolicited
40
+ Bulk
41
+ Email
42
+
43
+ If your spam filter supports it, the GTUBE provides a test by which you
44
+ can verify that the filter is installed correctly and is detecting incoming
45
+ spam. You can send yourself a test mail containing the following string of
46
+ characters (in upper case and with no white spaces and line breaks):
47
+
48
+ XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
49
+
50
+ You should send this test mail from an account outside of your network.
51
+ """
52
+
53
+ VIRUS_BODY = r"""Subject: Virus Test Message (EICAR)
54
+ MIME-Version: 1.0
55
+ Content-Type: multipart/mixed; boundary="huq684BweRXVnRxX"
56
+ Content-Disposition: inline
57
+ Date: Sun, 06 Nov 2011 10:08:18 -0800
58
+
59
+
60
+ --huq684BweRXVnRxX
61
+ Content-Type: text/plain; charset=us-ascii
62
+ Content-Disposition: inline
63
+
64
+
65
+ This is a virus test message. It contains an attached file 'eicar.com',
66
+ which contains the EICAR virus <http://eicar.org/86-0-Intended-use.html>
67
+ test pattern.
68
+
69
+
70
+ --huq684BweRXVnRxX
71
+ Content-Type: application/x-msdos-program
72
+ Content-Disposition: attachment; filename="eicar.com"
73
+ Content-Transfer-Encoding: quoted-printable
74
+
75
+ X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*=0A
76
+ --huq684BweRXVnRxX--
77
+ """
78
+
79
+
80
+ class MaddrFactory(factory.django.DjangoModelFactory):
81
+ """Factory for Maddr."""
82
+
83
+ class Meta:
84
+ model = models.Maddr
85
+ django_get_or_create = ("email",)
86
+
87
+ id = factory.Sequence(lambda n: n) # NOQA:A003
88
+ email = factory.Sequence(lambda n: f"user_{n}@domain.test")
89
+ domain = "test.domain"
90
+
91
+
92
+ class MsgsFactory(factory.django.DjangoModelFactory):
93
+ """Factory for Mailaddr."""
94
+
95
+ class Meta:
96
+ model = models.Msgs
97
+
98
+ mail_id = factory.Sequence(lambda n: f"mailid{n}".encode("ascii"))
99
+ secret_id = factory.Sequence(lambda n: smart_bytes(f"id{n}"))
100
+ sid = factory.SubFactory(MaddrFactory)
101
+ client_addr = "127.0.0.1"
102
+ originating = "Y"
103
+ dsn_sent = "N"
104
+ subject = factory.Sequence(lambda n: f"Test message {n}")
105
+ time_num = factory.LazyAttribute(lambda o: int(time.time()))
106
+ time_iso = factory.LazyAttribute(
107
+ lambda o: datetime.datetime.fromtimestamp(o.time_num).isoformat()
108
+ )
109
+ size = 100
110
+
111
+
112
+ class MsgrcptFactory(factory.django.DjangoModelFactory):
113
+ """Factory for Msgrcpt."""
114
+
115
+ class Meta:
116
+ model = models.Msgrcpt
117
+
118
+ rseqnum = 1
119
+ is_local = "Y"
120
+ bl = "N"
121
+ wl = "N"
122
+ mail = factory.SubFactory(MsgsFactory)
123
+ rid = factory.SubFactory(MaddrFactory)
124
+
125
+
126
+ class QuarantineFactory(factory.django.DjangoModelFactory):
127
+ """Factory for Quarantine."""
128
+
129
+ class Meta:
130
+ model = models.Quarantine
131
+
132
+ chunk_ind = 1
133
+ mail = factory.SubFactory(MsgsFactory)
134
+
135
+
136
+ def create_quarantined_msg(rcpt, sender, rs, body, **kwargs):
137
+ """Create a quarantined msg."""
138
+ msgrcpt = MsgrcptFactory(
139
+ rs=rs,
140
+ rid__email=rcpt,
141
+ rid__domain="com.test", # FIXME
142
+ mail__sid__email=smart_bytes(sender),
143
+ mail__sid__domain="", # FIXME
144
+ **kwargs,
145
+ )
146
+ QuarantineFactory(
147
+ mail=msgrcpt.mail,
148
+ mail_text=smart_bytes(SPAM_BODY.format(rcpt=rcpt, sender=sender)),
149
+ )
150
+ return msgrcpt
151
+
152
+
153
+ def create_spam(rcpt, sender="spam@evil.corp", rs=" "):
154
+ """Create a spam."""
155
+ body = SPAM_BODY.format(rcpt=rcpt, sender=sender)
156
+ body += "fóó bár"
157
+ return create_quarantined_msg(
158
+ rcpt, sender, rs, body, bspam_level=999.0, content="S"
159
+ )
160
+
161
+
162
+ def create_virus(rcpt, sender="virus@evil.corp", rs=" "):
163
+ """Create a virus."""
164
+ return create_quarantined_msg(rcpt, sender, rs, VIRUS_BODY, content="V")