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.
- modoboa/admin/api/v1/serializers.py +1 -0
- modoboa/admin/api/v2/serializers.py +15 -1
- modoboa/admin/api/v2/tests.py +11 -1
- modoboa/admin/lib.py +1 -1
- modoboa/admin/models/mailbox.py +15 -13
- modoboa/admin/models/mxrecord.py +4 -0
- modoboa/admin/tests/test_import_.py +11 -11
- modoboa/amavis/__init__.py +3 -0
- modoboa/amavis/app_settings.py +276 -0
- modoboa/amavis/apps.py +18 -0
- modoboa/amavis/checks/__init__.py +2 -0
- modoboa/amavis/checks/settings_checks.py +59 -0
- modoboa/amavis/dbrouter.py +35 -0
- modoboa/amavis/factories.py +164 -0
- modoboa/amavis/handlers.py +146 -0
- modoboa/amavis/lib.py +381 -0
- modoboa/amavis/management/__init__.py +0 -0
- modoboa/amavis/management/commands/__init__.py +0 -0
- modoboa/amavis/management/commands/amnotify.py +99 -0
- modoboa/amavis/management/commands/qcleanup.py +84 -0
- modoboa/amavis/migrations/0001_initial.py +340 -0
- modoboa/amavis/migrations/__init__.py +0 -0
- modoboa/amavis/models.py +226 -0
- modoboa/amavis/serializers.py +139 -0
- modoboa/amavis/sql_connector.py +240 -0
- modoboa/amavis/sql_email.py +66 -0
- modoboa/amavis/tasks.py +33 -0
- modoboa/amavis/templates/amavis/notifications/pending_requests.html +16 -0
- modoboa/amavis/tests/__init__.py +0 -0
- modoboa/amavis/tests/sa-learn +3 -0
- modoboa/amavis/tests/sample_messages/quarantined-input.txt +80 -0
- modoboa/amavis/tests/sample_messages/quarantined-output-plain_nolinks.txt +17 -0
- modoboa/amavis/tests/spamc +3 -0
- modoboa/amavis/tests/test_checks.py +25 -0
- modoboa/amavis/tests/test_handlers.py +214 -0
- modoboa/amavis/tests/test_lib.py +90 -0
- modoboa/amavis/tests/test_management_commands.py +45 -0
- modoboa/amavis/tests/test_sql_email.py +67 -0
- modoboa/amavis/tests/test_utils.py +19 -0
- modoboa/amavis/tests/test_viewsets.py +319 -0
- modoboa/amavis/urls.py +11 -0
- modoboa/amavis/utils.py +105 -0
- modoboa/amavis/viewsets.py +265 -0
- modoboa/core/api/v1/serializers.py +7 -5
- modoboa/core/api/v2/serializers.py +13 -2
- modoboa/core/api/v2/tests.py +34 -4
- modoboa/core/api/v2/urls.py +10 -5
- modoboa/core/api/v2/views.py +23 -2
- modoboa/core/api/v2/viewsets.py +24 -3
- modoboa/core/app_settings.py +11 -0
- modoboa/core/fido2_auth.py +1 -2
- modoboa/core/handlers.py +6 -2
- modoboa/core/management/commands/add_allowed_hosts.py +33 -0
- modoboa/core/management/commands/cleanlogs.py +9 -0
- modoboa/core/management/commands/load_initial_data.py +10 -0
- modoboa/core/migrations/0025_rename_user_email_is_active_core_user_email_c0c03f_idx.py +23 -5
- modoboa/core/tests/test_core.py +29 -0
- modoboa/core/utils.py +6 -0
- modoboa/dnstools/api/v2/serializers.py +9 -11
- modoboa/frontend_dist/assets/AccountAliasForm-BuSy_1n9.js +1 -0
- modoboa/frontend_dist/assets/AccountEditView-qdJmLM_e.js +1 -0
- modoboa/frontend_dist/assets/AccountLayout-DrN7vHsX.js +1 -0
- modoboa/frontend_dist/assets/AccountPasswordSubForm-DZGt_Xgq.js +1 -0
- modoboa/frontend_dist/assets/AccountView-CO65y0vZ.js +1 -0
- modoboa/frontend_dist/assets/AddressBook-BZNUlhek.js +1 -0
- modoboa/frontend_dist/assets/AdminLayout-CTNhuwTw.js +1 -0
- modoboa/frontend_dist/assets/AlarmsView-9yKGbmkC.css +1 -0
- modoboa/frontend_dist/assets/AlarmsView-DN_JIw9g.js +1 -0
- modoboa/frontend_dist/assets/AliasEditView-DjpPUTp9.js +1 -0
- modoboa/frontend_dist/assets/{AliasRecipientForm-DVZXWaUX.js → AliasRecipientForm-B1Y8wFdP.js} +1 -1
- modoboa/frontend_dist/assets/AliasView-GOJ5lyQH.js +1 -0
- modoboa/frontend_dist/assets/AuditTrailView-fbXmq70e.js +1 -0
- modoboa/frontend_dist/assets/CalendarView-LlQQNEPL.js +1 -0
- modoboa/frontend_dist/assets/{ChoiceField-7eU7c_rI.js → ChoiceField-B3ReQHVe.js} +1 -1
- modoboa/frontend_dist/assets/ComposeEmailForm-Bs1fZXAL.js +1 -0
- modoboa/frontend_dist/assets/ComposeEmailView-s3LMl3pO.js +1 -0
- modoboa/frontend_dist/assets/ConfirmDialog-DY_kUHLG.js +1 -0
- modoboa/frontend_dist/assets/{ConnectedLayout-BaJZ3BeR.css → ConnectedLayout-Bxh21hcH.css} +1 -1
- modoboa/frontend_dist/assets/ConnectedLayout-UWjiYBNw.js +1 -0
- modoboa/frontend_dist/assets/CreationForm-ORg3fazt.js +1 -0
- modoboa/frontend_dist/assets/DashboardView-Dplk9itS.js +1 -0
- modoboa/frontend_dist/assets/{DashboardView-BLlMi6Qb.css → DashboardView-gwwVAPvt.css} +1 -1
- modoboa/frontend_dist/assets/DomainAdminList-DVn9x0rB.js +1 -0
- modoboa/frontend_dist/assets/DomainEditView-nAoL64D_.js +1 -0
- modoboa/frontend_dist/assets/{DomainTransportForm-DPnPGBOp.js → DomainTransportForm-CA-DNUxX.js} +1 -1
- modoboa/frontend_dist/assets/{DomainView-BDKoBFYr.css → DomainView-CCLYXPHx.css} +1 -1
- modoboa/frontend_dist/assets/DomainView-CdXPpwJG.js +5 -0
- modoboa/frontend_dist/assets/DomainsView-B_59gowf.js +1 -0
- modoboa/frontend_dist/assets/DomainsView-DZ-ss9bI.css +1 -0
- modoboa/frontend_dist/assets/EmailField-CwcwI5xW.js +1 -0
- modoboa/frontend_dist/assets/EmailView-BshxcfAK.js +1 -0
- modoboa/frontend_dist/assets/EmptyLayout-DFfhnhLi.js +1 -0
- modoboa/frontend_dist/assets/FiltersView-Cf20MSTK.js +1 -0
- modoboa/frontend_dist/assets/ForwardEmailView-CZG062os.js +1 -0
- modoboa/frontend_dist/assets/{HtmlEditor-uM4AtIGi.js → HtmlEditor-Bh4c689R.js} +1 -1
- modoboa/frontend_dist/assets/IdentitiesView-BXAuU1YX.js +1 -0
- modoboa/frontend_dist/assets/{IdentitiesView-jmuItyMZ.css → IdentitiesView-DPrrRMS5.css} +1 -1
- modoboa/frontend_dist/assets/InformationView-C9vvvQhJ.css +1 -0
- modoboa/frontend_dist/assets/InformationView-Cn5FZW7H.js +1 -0
- modoboa/frontend_dist/assets/{LoadingData-CVD2Aen8.js → LoadingData-CdVvm4FI.js} +1 -1
- modoboa/frontend_dist/assets/{LoginCallbackView-sWzBke1g.js → LoginCallbackView-B9hAH4MI.js} +1 -1
- modoboa/frontend_dist/assets/{LoginView-wmN73W0f.js → LoginView-tHIR4Adc.js} +1 -1
- modoboa/frontend_dist/assets/MailboxView-Bugu2vhg.js +1 -0
- modoboa/frontend_dist/assets/MenuItems-PXjiG-fs.js +1 -0
- modoboa/frontend_dist/assets/MessageView-Cy4STShm.js +1 -0
- modoboa/frontend_dist/assets/MessagesView-DdkuEgfX.js +1 -0
- modoboa/frontend_dist/assets/MigrationsView-CidSEjCF.js +1 -0
- modoboa/frontend_dist/assets/{ParametersForm-BCeQljir.js → ParametersForm-CAv4SH-E.js} +1 -1
- modoboa/frontend_dist/assets/ParametersView-CX7Ffemw.js +1 -0
- modoboa/frontend_dist/assets/ParametersView-CrbNcmV3.js +1 -0
- modoboa/frontend_dist/assets/ProviderEditView-CrltAQXl.js +1 -0
- modoboa/frontend_dist/assets/ProviderGeneralForm-BYAzVnXM.js +1 -0
- modoboa/frontend_dist/assets/ProvidersView-osjIY4Ex.js +1 -0
- modoboa/frontend_dist/assets/QuarantineLayout-B8EcU9vS.js +1 -0
- modoboa/frontend_dist/assets/QuarantineView-D4gOE4EQ.css +1 -0
- modoboa/frontend_dist/assets/QuarantineView-D8Qg0MXA.js +1 -0
- modoboa/frontend_dist/assets/ReplyEmailView-BABPqWhd.js +1 -0
- modoboa/frontend_dist/assets/ResourcesForm-OaqdRYVs.js +1 -0
- modoboa/frontend_dist/assets/SelfServiceLayout-d277YTGR.js +1 -0
- modoboa/frontend_dist/assets/SettingsView-9iNcDhkI.js +6 -0
- modoboa/frontend_dist/assets/StatisticsView-cHsPyGkL.js +1 -0
- modoboa/frontend_dist/assets/TimeSerieChart--V83dcJ9.js +1 -0
- modoboa/frontend_dist/assets/UserLayout-B3sBiTcZ.js +1 -0
- modoboa/frontend_dist/assets/VAlert-BuaaYN2h.js +1 -0
- modoboa/frontend_dist/assets/VApp-CKP-6zGP.js +1 -0
- modoboa/frontend_dist/assets/VAutocomplete-Dwv6_Rzq.js +1 -0
- modoboa/frontend_dist/assets/VAvatar-Cmga0vj6.js +1 -0
- modoboa/frontend_dist/assets/VBadge-BQrRJ9S0.css +1 -0
- modoboa/frontend_dist/assets/VBadge-CixeK87a.js +1 -0
- modoboa/frontend_dist/assets/VCard-CxH9DWoK.js +1 -0
- modoboa/frontend_dist/assets/VCheckbox-62GOpvvP.js +1 -0
- modoboa/frontend_dist/assets/{VCheckboxBtn-j7di4leN.js → VCheckboxBtn-DMoNtKT8.js} +1 -1
- modoboa/frontend_dist/assets/VChip-D_styETR.js +1 -0
- modoboa/frontend_dist/assets/VColorPicker-BHscBGQV.js +1 -0
- modoboa/frontend_dist/assets/VContainer-B46caNs1.js +1 -0
- modoboa/frontend_dist/assets/VDataTable-Bh8NbVSx.js +1 -0
- modoboa/frontend_dist/assets/VDataTableServer-BDR5hOmo.js +1 -0
- modoboa/frontend_dist/assets/VDataTableVirtual-BOQlNtIG.js +1 -0
- modoboa/frontend_dist/assets/{VDialog-CZqM2Ofu.js → VDialog-BcTg7w6P.js} +1 -1
- modoboa/frontend_dist/assets/VExpansionPanels-BmH5Jl2Z.js +1 -0
- modoboa/frontend_dist/assets/VFileInput-BC4yAygd.js +1 -0
- modoboa/frontend_dist/assets/VForm-D5iPGkde.js +1 -0
- modoboa/frontend_dist/assets/VInput-CcxkaOXT.css +1 -0
- modoboa/frontend_dist/assets/VInput-CoDJzvaW.js +1 -0
- modoboa/frontend_dist/assets/VMenu-gUG70-zD.js +1 -0
- modoboa/frontend_dist/assets/VPicker-BXuKT3zB.js +1 -0
- modoboa/frontend_dist/assets/VProgressCircular-BtOPiGCg.js +1 -0
- modoboa/frontend_dist/assets/VRadioGroup-DIFZKSn-.js +1 -0
- modoboa/frontend_dist/assets/{VRow-C_Ydf6yr.js → VRow-ozg66L7j.js} +1 -1
- modoboa/frontend_dist/assets/VSelect-C3RjAa45.js +1 -0
- modoboa/frontend_dist/assets/VSelectionControl-zyz-fJvC.js +1 -0
- modoboa/frontend_dist/assets/VSheet-BNx2X4Mk.js +1 -0
- modoboa/frontend_dist/assets/VSpacer-DinPiXs9.js +1 -0
- modoboa/frontend_dist/assets/VSwitch-DwxdeAEq.js +1 -0
- modoboa/frontend_dist/assets/VTable-DaLxa4FO.js +1 -0
- modoboa/frontend_dist/assets/VTabs-BP0Hgsgm.js +1 -0
- modoboa/frontend_dist/assets/VTextField-BzBVKKob.css +1 -0
- modoboa/frontend_dist/assets/VTextField-XoGTj1KG.js +1 -0
- modoboa/frontend_dist/assets/VTextarea-wBlRMIv_.js +1 -0
- modoboa/frontend_dist/assets/VToolbar-CFZfqeOr.js +1 -0
- modoboa/frontend_dist/assets/VWindowItem-BB7ETW3b.js +1 -0
- modoboa/frontend_dist/assets/WebmailLayout-_Hk1XhVq.js +1 -0
- modoboa/frontend_dist/assets/accounts-DUzbx6k8.js +1 -0
- modoboa/frontend_dist/assets/admin-DewTk2H8.js +1 -0
- modoboa/frontend_dist/assets/{aliases-c3n-dCV_.js → aliases-4sXmjwXp.js} +1 -1
- modoboa/frontend_dist/assets/amavis-CC0li7_T.js +1 -0
- modoboa/frontend_dist/assets/amavis-DK8SHE6o.js +1 -0
- modoboa/frontend_dist/assets/{contacts-Bqckz8sr.js → contacts-BjghrPqZ.js} +1 -1
- modoboa/frontend_dist/assets/{domains-nBMR-fRf.js → domains-BSawReeu.js} +1 -1
- modoboa/frontend_dist/assets/{domains.store-ChZgLcqP.js → domains.store-D-vWCEIK.js} +1 -1
- modoboa/frontend_dist/assets/{filter-D7NrAf6a.js → filter-C82FUCw_.js} +1 -1
- modoboa/frontend_dist/assets/forwardRefs-cvcnlhoK.js +1 -0
- modoboa/frontend_dist/assets/global.store-DbkcI5o2.js +1 -0
- modoboa/frontend_dist/assets/{importExport-Dn9vYw7T.js → importExport-DzoL4Mvc.js} +1 -1
- modoboa/frontend_dist/assets/index-BImkz5Jx.js +984 -0
- modoboa/frontend_dist/assets/index-DuzUMVLM.js +1 -0
- modoboa/frontend_dist/assets/layout-C5FyYCHK.js +1 -0
- modoboa/frontend_dist/assets/{layout.store-BxBoBlgf.js → layout.store-NXWtFIwL.js} +1 -1
- modoboa/frontend_dist/assets/logos-BswdveCV.js +1 -0
- modoboa/frontend_dist/assets/{logs-DrTzylW7.js → logs-6CbtfaZS.js} +1 -1
- modoboa/frontend_dist/assets/{parameters-Lgiqp7aw.js → parameters-aSQiR7kN.js} +1 -1
- modoboa/frontend_dist/assets/{parameters.store-C9k9DuTj.js → parameters.store-CzQqVatx.js} +1 -1
- modoboa/frontend_dist/assets/permissions-DNoefz-n.js +1 -0
- modoboa/frontend_dist/assets/{ssrBoot-BsxW6uW4.js → ssrBoot-CKUX4kcb.js} +1 -1
- modoboa/frontend_dist/assets/{tag-CFK9dzMJ.js → tag-B_yWNNJD.js} +1 -1
- modoboa/frontend_dist/assets/transports-BDNB9wR5.js +1 -0
- modoboa/frontend_dist/assets/{webmail-KrD8ZhNM.js → webmail-CdU6CD9b.js} +1 -1
- modoboa/frontend_dist/index.html +1 -1
- modoboa/lib/email_utils.py +2 -2
- modoboa/lib/permissions.py +7 -0
- modoboa/lib/redis.py +1 -5
- modoboa/lib/test_runners.py +29 -0
- modoboa/lib/tests/__init__.py +5 -1
- modoboa/locale/br/LC_MESSAGES/django.po +87 -75
- modoboa/locale/cs/LC_MESSAGES/django.po +82 -74
- modoboa/locale/cs_CZ/LC_MESSAGES/django.po +145 -121
- modoboa/locale/de/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/de/LC_MESSAGES/django.po +339 -651
- modoboa/locale/de_DE/LC_MESSAGES/django.po +87 -75
- modoboa/locale/el_GR/LC_MESSAGES/django.po +160 -135
- modoboa/locale/en/LC_MESSAGES/django.po +82 -74
- modoboa/locale/es/LC_MESSAGES/django.po +158 -131
- modoboa/locale/es_MX/LC_MESSAGES/django.po +82 -74
- modoboa/locale/fi/LC_MESSAGES/django.po +87 -75
- modoboa/locale/fr/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/fr/LC_MESSAGES/django.po +469 -201
- modoboa/locale/hu/LC_MESSAGES/django.po +82 -74
- modoboa/locale/it/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/it/LC_MESSAGES/django.po +148 -122
- modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ja_JP/LC_MESSAGES/django.po +201 -334
- modoboa/locale/ka/LC_MESSAGES/django.po +82 -74
- modoboa/locale/nl_NL/LC_MESSAGES/django.po +160 -132
- modoboa/locale/no/LC_MESSAGES/django.po +82 -74
- modoboa/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pl_PL/LC_MESSAGES/django.po +172 -149
- modoboa/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pt_BR/LC_MESSAGES/django.po +172 -144
- modoboa/locale/pt_PT/LC_MESSAGES/django.po +135 -112
- modoboa/locale/ro_RO/LC_MESSAGES/django.po +87 -75
- modoboa/locale/ru/LC_MESSAGES/django.po +142 -118
- modoboa/locale/si/LC_MESSAGES/django.po +82 -74
- modoboa/locale/sk/LC_MESSAGES/django.po +82 -74
- modoboa/locale/sk_SK/LC_MESSAGES/django.po +84 -76
- modoboa/locale/sl_SI/LC_MESSAGES/django.po +90 -76
- modoboa/locale/sv/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/sv/LC_MESSAGES/django.po +172 -139
- modoboa/locale/tr/LC_MESSAGES/django.po +87 -75
- modoboa/locale/tr_TR/LC_MESSAGES/django.po +84 -74
- modoboa/locale/uk/LC_MESSAGES/django.po +82 -74
- modoboa/locale/zh/LC_MESSAGES/django.po +82 -74
- modoboa/locale/zh_CN/LC_MESSAGES/django.po +82 -74
- modoboa/locale/zh_TW/LC_MESSAGES/django.po +87 -75
- modoboa/parameters/api/v2/tests.py +2 -2
- modoboa/parameters/api/v2/viewsets.py +2 -0
- modoboa/policyd/tests.py +2 -0
- modoboa/urls_api_v2.py +6 -0
- {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/METADATA +6 -4
- {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/RECORD +244 -193
- modoboa/frontend_dist/assets/AccountAliasForm-DVXatAhB.js +0 -1
- modoboa/frontend_dist/assets/AccountEditView-DmvQjxpx.js +0 -1
- modoboa/frontend_dist/assets/AccountLayout-OGtZvlHR.js +0 -1
- modoboa/frontend_dist/assets/AccountPasswordSubForm-g3IEGrgM.js +0 -1
- modoboa/frontend_dist/assets/AccountView-DsxYqr3k.js +0 -1
- modoboa/frontend_dist/assets/AddressBook-3RoKiKon.js +0 -1
- modoboa/frontend_dist/assets/AdminLayout-CWfn8zaQ.js +0 -1
- modoboa/frontend_dist/assets/AlarmsView-Bheey-gp.css +0 -1
- modoboa/frontend_dist/assets/AlarmsView-D3Mh8ntf.js +0 -1
- modoboa/frontend_dist/assets/AliasEditView-C15eUZ11.js +0 -1
- modoboa/frontend_dist/assets/AliasView-Cyvc5vMb.js +0 -1
- modoboa/frontend_dist/assets/AuditTrailView-LI2XuLLn.js +0 -1
- modoboa/frontend_dist/assets/CalendarView-BpOlPh3f.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailForm-CNfI7ept.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailView-B866Xsrc.js +0 -1
- modoboa/frontend_dist/assets/ConfirmDialog-BvqxQsD1.js +0 -1
- modoboa/frontend_dist/assets/ConnectedLayout-BQ3ug6Jh.js +0 -1
- modoboa/frontend_dist/assets/CreationForm-C9Kh05ax.js +0 -1
- modoboa/frontend_dist/assets/DashboardView-Cw-gIcuB.js +0 -1
- modoboa/frontend_dist/assets/DomainAdminList-CsWUNKVk.js +0 -1
- modoboa/frontend_dist/assets/DomainEditView-DocxeOeW.js +0 -1
- modoboa/frontend_dist/assets/DomainView-Djc_0PsF.js +0 -5
- modoboa/frontend_dist/assets/DomainsView-B-Lxru7P.js +0 -1
- modoboa/frontend_dist/assets/DomainsView-DasJ0NdZ.css +0 -1
- modoboa/frontend_dist/assets/EmailField-BEKxuYni.js +0 -1
- modoboa/frontend_dist/assets/EmailView-BsR1Wes5.js +0 -1
- modoboa/frontend_dist/assets/EmptyLayout-BzPFOeLU.js +0 -1
- modoboa/frontend_dist/assets/FiltersView-5rmpC5cC.js +0 -1
- modoboa/frontend_dist/assets/ForwardEmailView-D7MbetcT.js +0 -1
- modoboa/frontend_dist/assets/IdentitiesView-BqjD9Lue.js +0 -1
- modoboa/frontend_dist/assets/InformationView-CghcvPn2.js +0 -1
- modoboa/frontend_dist/assets/InformationView-U5Ww-Sx1.css +0 -1
- modoboa/frontend_dist/assets/MailboxView-T_p-_ZtJ.js +0 -1
- modoboa/frontend_dist/assets/MenuItems-kHCMzR5E.js +0 -1
- modoboa/frontend_dist/assets/MessagesView-Cerv3xsy.js +0 -1
- modoboa/frontend_dist/assets/MigrationsView-7kjqPyYU.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-DspBxVMV.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-Dy0H5ep1.js +0 -1
- modoboa/frontend_dist/assets/ProviderEditView-DDLMOylC.js +0 -1
- modoboa/frontend_dist/assets/ProviderGeneralForm-BwOSKNHK.js +0 -1
- modoboa/frontend_dist/assets/ProvidersView-C99UD0WB.js +0 -1
- modoboa/frontend_dist/assets/ReplyEmailView-DAPBHldd.js +0 -1
- modoboa/frontend_dist/assets/ResourcesForm-D87PHeH0.js +0 -1
- modoboa/frontend_dist/assets/SettingsView-OxDo9wNd.js +0 -6
- modoboa/frontend_dist/assets/StatisticsView-CM__Eqku.js +0 -1
- modoboa/frontend_dist/assets/TimeSerieChart-C6j0uYR_.js +0 -1
- modoboa/frontend_dist/assets/UserLayout-CckCGnPS.js +0 -1
- modoboa/frontend_dist/assets/VAlert-B7mzOJIO.js +0 -1
- modoboa/frontend_dist/assets/VApp-BKxnjOoi.js +0 -1
- modoboa/frontend_dist/assets/VAutocomplete-Cc4_tcl1.js +0 -1
- modoboa/frontend_dist/assets/VAvatar-BAgTUIvX.js +0 -1
- modoboa/frontend_dist/assets/VCard-DFWiFORP.js +0 -1
- modoboa/frontend_dist/assets/VCheckbox-CKsH_vq3.js +0 -1
- modoboa/frontend_dist/assets/VChip-nZ0uhY7t.js +0 -1
- modoboa/frontend_dist/assets/VColorPicker-Os2aeP6J.js +0 -1
- modoboa/frontend_dist/assets/VContainer-Btam4lk2.js +0 -1
- modoboa/frontend_dist/assets/VDataTable-D_0_xJTl.js +0 -1
- modoboa/frontend_dist/assets/VDataTableServer-BWTt4Mzi.js +0 -1
- modoboa/frontend_dist/assets/VDataTableVirtual-BwVmkt4u.js +0 -1
- modoboa/frontend_dist/assets/VExpansionPanels-B5D6GOa3.js +0 -1
- modoboa/frontend_dist/assets/VFileInput-Cv9DIPki.js +0 -1
- modoboa/frontend_dist/assets/VForm-CpoZf60D.js +0 -1
- modoboa/frontend_dist/assets/VMenu-B_dVqOmo.js +0 -1
- modoboa/frontend_dist/assets/VPicker-CXkIGEze.js +0 -1
- modoboa/frontend_dist/assets/VProgressCircular-CrEXxs7k.js +0 -1
- modoboa/frontend_dist/assets/VRadioGroup-D8ypjYOO.js +0 -1
- modoboa/frontend_dist/assets/VSelect-CS51PDEt.js +0 -1
- modoboa/frontend_dist/assets/VSelectionControl-DiOqtY38.js +0 -1
- modoboa/frontend_dist/assets/VSheet-5VVWtHvs.js +0 -1
- modoboa/frontend_dist/assets/VSpacer-C6PZ3X24.js +0 -1
- modoboa/frontend_dist/assets/VSwitch-Chg5o-Cp.js +0 -1
- modoboa/frontend_dist/assets/VTable-KsiZ3cBz.js +0 -1
- modoboa/frontend_dist/assets/VTabs-tNrJIYO0.js +0 -1
- modoboa/frontend_dist/assets/VTextField-C-J20yj_.css +0 -1
- modoboa/frontend_dist/assets/VTextField-CLwRV0Cb.js +0 -1
- modoboa/frontend_dist/assets/VTextarea-Di8jbl8m.js +0 -1
- modoboa/frontend_dist/assets/VToolbar-CGwhgdmI.js +0 -1
- modoboa/frontend_dist/assets/VWindowItem-wWSFAGI-.js +0 -1
- modoboa/frontend_dist/assets/WebmailLayout-DsYThBrz.js +0 -1
- modoboa/frontend_dist/assets/admin-CJVLMHh9.js +0 -1
- modoboa/frontend_dist/assets/forwardRefs-Cpc3YYl6.js +0 -1
- modoboa/frontend_dist/assets/global.store-DUP26-A5.js +0 -1
- modoboa/frontend_dist/assets/index-DV9Li2cg.js +0 -852
- modoboa/frontend_dist/assets/index-DzL89N4E.js +0 -1
- modoboa/frontend_dist/assets/layout-CHO37cA6.js +0 -1
- modoboa/frontend_dist/assets/permissions-mL5hLHcW.js +0 -1
- modoboa/frontend_dist/assets/transports-TO08iTXJ.js +0 -1
- {modoboa-2.4.10.data → modoboa-2.5.0.data}/scripts/modoboa-admin.py +0 -0
- {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/WHEEL +0 -0
- {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/entry_points.txt +0 -0
- {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/licenses/LICENSE +0 -0
- {modoboa-2.4.10.dist-info → modoboa-2.5.0.dist-info}/top_level.txt +0 -0
modoboa/amavis/tasks.py
ADDED
|
@@ -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,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" >© 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'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,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)
|