modoboa 2.4.9__py3-none-any.whl → 2.4.11__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 +8 -1
- modoboa/admin/api/v2/serializers.py +5 -3
- 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/core/api/v2/serializers.py +43 -15
- modoboa/core/api/v2/tests.py +18 -0
- modoboa/core/api/v2/urls.py +5 -0
- modoboa/core/api/v2/views.py +20 -1
- modoboa/core/app_settings.py +12 -1
- modoboa/core/fido2_auth.py +1 -2
- modoboa/core/management/commands/cleanlogs.py +9 -0
- modoboa/core/management/commands/load_initial_data.py +7 -15
- modoboa/core/migrations/0025_rename_user_email_is_active_core_user_email_c0c03f_idx.py +5 -5
- modoboa/core/tests/test_core.py +5 -0
- modoboa/core/utils.py +3 -0
- modoboa/dnstools/api/v2/serializers.py +9 -11
- modoboa/frontend_dist/assets/{AccountAliasForm-DO4IpT1M.js → AccountAliasForm-BV6KvTu6.js} +1 -1
- modoboa/frontend_dist/assets/{AccountEditView-BLA0CnKd.js → AccountEditView-DDOFyfBD.js} +1 -1
- modoboa/frontend_dist/assets/AccountLayout-rX51xgxT.js +1 -0
- modoboa/frontend_dist/assets/{AccountPasswordSubForm-D9xU4Or7.js → AccountPasswordSubForm-D9S6LaeH.js} +1 -1
- modoboa/frontend_dist/assets/AccountView-cmvaZNq3.js +1 -0
- modoboa/frontend_dist/assets/{AddressBook-CJM-ajGg.js → AddressBook-DCJxL8SU.js} +1 -1
- modoboa/frontend_dist/assets/AdminLayout-r0wfG2lO.js +1 -0
- modoboa/frontend_dist/assets/{AlarmsView-CpIExehE.js → AlarmsView-C0bqC4PA.js} +1 -1
- modoboa/frontend_dist/assets/{AliasEditView-Bu0rnLCQ.js → AliasEditView-DVoWoCGY.js} +1 -1
- modoboa/frontend_dist/assets/{AliasRecipientForm-CGuaOz6K.js → AliasRecipientForm-IOae6sjF.js} +1 -1
- modoboa/frontend_dist/assets/{AliasView-Cdwd7AvE.js → AliasView-DrONZXOh.js} +1 -1
- modoboa/frontend_dist/assets/{AuditTrailView-Dn1B2S23.js → AuditTrailView-OTkoZaMU.js} +1 -1
- modoboa/frontend_dist/assets/{CalendarView-CV-DAWtd.js → CalendarView-CqF4_Ui9.js} +1 -1
- modoboa/frontend_dist/assets/{ChoiceField-BS45Bjds.js → ChoiceField-DJ_c78Cm.js} +1 -1
- modoboa/frontend_dist/assets/{ComposeEmailForm-22JKKl9M.js → ComposeEmailForm-DO5_GB3e.js} +1 -1
- modoboa/frontend_dist/assets/ComposeEmailView-A91HCBsN.js +1 -0
- modoboa/frontend_dist/assets/{ConfirmDialog-BLu1kBWp.js → ConfirmDialog-BBcgdAnO.js} +1 -1
- modoboa/frontend_dist/assets/ConnectedLayout-1oRW-Rql.js +1 -0
- modoboa/frontend_dist/assets/{ConnectedLayout-AhwwAZvE.css → ConnectedLayout-Dvwmicnc.css} +1 -1
- modoboa/frontend_dist/assets/CreationForm-71YJbjsA.js +1 -0
- modoboa/frontend_dist/assets/CreationForm-HaiQhqwK.css +1 -0
- modoboa/frontend_dist/assets/DashboardView-CdLpSfUl.js +1 -0
- modoboa/frontend_dist/assets/{DashboardView-BLlMi6Qb.css → DashboardView-gwwVAPvt.css} +1 -1
- modoboa/frontend_dist/assets/DomainAdminList-BjC4KsqI.js +1 -0
- modoboa/frontend_dist/assets/DomainEditView-CQjKwYxl.js +1 -0
- modoboa/frontend_dist/assets/DomainTransportForm-C2xo0Yd7.js +1 -0
- modoboa/frontend_dist/assets/{DomainView-DCfSLvj-.css → DomainView-BDKoBFYr.css} +1 -1
- modoboa/frontend_dist/assets/DomainView-BhhuZI_N.js +5 -0
- modoboa/frontend_dist/assets/DomainsView-Cft4BP8Z.js +1 -0
- modoboa/frontend_dist/assets/{DomainsView-BUgJLN9o.css → DomainsView-DasJ0NdZ.css} +1 -1
- modoboa/frontend_dist/assets/{EmailField-phdi2rhQ.js → EmailField-C8umy0EU.js} +1 -1
- modoboa/frontend_dist/assets/{EmailView-B8eqs4jh.js → EmailView-ki7uEQPD.js} +1 -1
- modoboa/frontend_dist/assets/EmptyLayout-DaA1XH9n.js +1 -0
- modoboa/frontend_dist/assets/{FiltersView-Dq_lXOZQ.js → FiltersView-FYFZxG4B.js} +1 -1
- modoboa/frontend_dist/assets/ForwardEmailView-cUbnSYCF.js +1 -0
- modoboa/frontend_dist/assets/{HtmlEditor-KH0pmxrK.js → HtmlEditor-CJ9umKeO.js} +1 -1
- modoboa/frontend_dist/assets/IdentitiesView-0ziuQ5s-.css +1 -0
- modoboa/frontend_dist/assets/IdentitiesView-njNo8N5n.js +1 -0
- modoboa/frontend_dist/assets/{InformationView-DlwtlpvG.js → InformationView-D1h38POt.js} +1 -1
- modoboa/frontend_dist/assets/{LoadingData-BYDZn1pb.js → LoadingData-CYwX3Jpn.js} +1 -1
- modoboa/frontend_dist/assets/{LoginCallbackView-xSv3LV4_.js → LoginCallbackView-E01qkKn0.js} +1 -1
- modoboa/frontend_dist/assets/{LoginView-R5FtOIzW.js → LoginView-Cy4uFV9h.js} +1 -1
- modoboa/frontend_dist/assets/{MailboxView-esEw0ueS.js → MailboxView-IlrLWm_H.js} +1 -1
- modoboa/frontend_dist/assets/{MenuItems-Bck8ixGf.js → MenuItems-BAtHWzAE.js} +1 -1
- modoboa/frontend_dist/assets/MessagesView-OSpjixFq.js +1 -0
- modoboa/frontend_dist/assets/{MigrationsView-BHTt1qeH.js → MigrationsView-DKNOsVzF.js} +1 -1
- modoboa/frontend_dist/assets/Modoboa_RVB-BLEU-SANS-pKrnjsR_.png +0 -0
- modoboa/frontend_dist/assets/{ParametersForm-Bx5J7UU0.js → ParametersForm-BZM0QSvg.js} +1 -1
- modoboa/frontend_dist/assets/ParametersView-C4bXASiq.js +1 -0
- modoboa/frontend_dist/assets/ParametersView-CYXgNmc1.js +1 -0
- modoboa/frontend_dist/assets/{ProviderEditView-d2fBym_r.js → ProviderEditView-CyxCWTST.js} +1 -1
- modoboa/frontend_dist/assets/{ProviderGeneralForm-C9jkYxB7.js → ProviderGeneralForm-BYPjNHqB.js} +1 -1
- modoboa/frontend_dist/assets/ProvidersView-CxrMkRyk.js +1 -0
- modoboa/frontend_dist/assets/ReplyEmailView-Dkw9-N26.js +1 -0
- modoboa/frontend_dist/assets/{ResourcesForm-RZNnt_x1.js → ResourcesForm-CuUvrOdY.js} +1 -1
- modoboa/frontend_dist/assets/SettingsView-BxLJBFY0.js +6 -0
- modoboa/frontend_dist/assets/{StatisticsView-CABa0tgU.js → StatisticsView-BN7QsZMT.js} +1 -1
- modoboa/frontend_dist/assets/{TimeSerieChart-BjM9PGIT.js → TimeSerieChart-BMN8BeFZ.js} +1 -1
- modoboa/frontend_dist/assets/UserLayout-B6-JQg4F.js +1 -0
- modoboa/frontend_dist/assets/{VAlert-DSMD0lqo.js → VAlert-DIQTrRif.js} +1 -1
- modoboa/frontend_dist/assets/{VApp-D2FuKvLN.js → VApp-CpkYA7js.js} +1 -1
- modoboa/frontend_dist/assets/{VAutocomplete-CQ5LL_Nk.js → VAutocomplete-C4IpXyl8.js} +1 -1
- modoboa/frontend_dist/assets/{VAvatar-DbAmPjyA.js → VAvatar-Lpb-Dion.js} +1 -1
- modoboa/frontend_dist/assets/{VCard-BGXTuaE-.js → VCard-er_isjE_.js} +1 -1
- modoboa/frontend_dist/assets/{VCheckbox-BM1QzY5m.js → VCheckbox-D-u8JXP1.js} +1 -1
- modoboa/frontend_dist/assets/{VCheckboxBtn-DdHQE-gt.js → VCheckboxBtn-Dt810gWf.js} +1 -1
- modoboa/frontend_dist/assets/{VChip-Bm7znPZ6.js → VChip-B4iSpj8_.js} +1 -1
- modoboa/frontend_dist/assets/{VColorPicker-CYCnVu_Z.js → VColorPicker-BAjGDsXv.js} +1 -1
- modoboa/frontend_dist/assets/VContainer-DvTbsotR.js +1 -0
- modoboa/frontend_dist/assets/{VDataTable-Etpc99CI.js → VDataTable-4JRjbtgF.js} +1 -1
- modoboa/frontend_dist/assets/{VDataTableServer-BNNG6dBx.js → VDataTableServer-tIDT1m3-.js} +1 -1
- modoboa/frontend_dist/assets/{VDataTableVirtual-D9Km3t6e.js → VDataTableVirtual-BlnO18u_.js} +1 -1
- modoboa/frontend_dist/assets/{VDialog-0NvxFg1X.js → VDialog-Bk6EWNhz.js} +1 -1
- modoboa/frontend_dist/assets/{VExpansionPanels-eED_hKBs.js → VExpansionPanels-CwGtXDhr.js} +1 -1
- modoboa/frontend_dist/assets/{VFileInput-COIdDRxq.js → VFileInput-D1_7ZkO_.js} +1 -1
- modoboa/frontend_dist/assets/{VForm-DwwrfSN8.js → VForm-DAkW4nfy.js} +1 -1
- modoboa/frontend_dist/assets/{VMenu-CTyyRlRb.js → VMenu-BPFJwj2f.js} +1 -1
- modoboa/frontend_dist/assets/{VPicker-3NOHH3h1.js → VPicker-CfT82M8N.js} +1 -1
- modoboa/frontend_dist/assets/{VProgressCircular-1VQzh5PK.js → VProgressCircular-w75-3ogi.js} +1 -1
- modoboa/frontend_dist/assets/{VRadioGroup-CnRLqIcd.js → VRadioGroup-0j6DNC_k.js} +1 -1
- modoboa/frontend_dist/assets/{VRow-DgX40uiD.js → VRow-BF35mT1S.js} +1 -1
- modoboa/frontend_dist/assets/{VSelect-CeY-emKK.js → VSelect-Cs4ARbAS.js} +1 -1
- modoboa/frontend_dist/assets/{VSelectionControl-BYdVI5Q9.js → VSelectionControl-Dg-XyRRS.js} +1 -1
- modoboa/frontend_dist/assets/VSheet-Btq_Mu4s.js +1 -0
- modoboa/frontend_dist/assets/VSpacer-C7xukQmu.js +1 -0
- modoboa/frontend_dist/assets/{VSwitch-BSifpScd.js → VSwitch-Cs1NQrmk.js} +1 -1
- modoboa/frontend_dist/assets/{VTable-BeyU0Uhj.js → VTable-CNz2SGk4.js} +1 -1
- modoboa/frontend_dist/assets/{VTabs-Dzq8prKm.js → VTabs-B1fyVn4M.js} +1 -1
- modoboa/frontend_dist/assets/{VTextField-FBM5XwC5.js → VTextField-BdyvgvkG.js} +1 -1
- modoboa/frontend_dist/assets/{VTextarea-BwkVlXPS.js → VTextarea-DnOMpe0Q.js} +1 -1
- modoboa/frontend_dist/assets/{VToolbar-AQXTYlxS.js → VToolbar-BiCiBxBJ.js} +1 -1
- modoboa/frontend_dist/assets/{VWindowItem-p9SJ1V9Y.js → VWindowItem-ChWm_kz3.js} +1 -1
- modoboa/frontend_dist/assets/WebmailLayout-o4uEkp9e.js +1 -0
- modoboa/frontend_dist/assets/admin-o-HRGnmT.js +1 -0
- modoboa/frontend_dist/assets/{aliases-C-hC9If-.js → aliases-DDVeehyg.js} +1 -1
- modoboa/frontend_dist/assets/{contacts-DZDbAN4o.js → contacts-C84DY-Q1.js} +1 -1
- modoboa/frontend_dist/assets/{domains-CWdc-ODs.js → domains-Bgn4ixHL.js} +1 -1
- modoboa/frontend_dist/assets/{domains.store-B6sc48r9.js → domains.store-DTE-V7Y1.js} +1 -1
- modoboa/frontend_dist/assets/{filter-BSw3r225.js → filter-CnffiQAW.js} +1 -1
- modoboa/frontend_dist/assets/{forwardRefs-Cn7kyt_f.js → forwardRefs-Dvjn_Xq4.js} +1 -1
- modoboa/frontend_dist/assets/{global.store-IwCGIFoO.js → global.store-BaiD63EN.js} +1 -1
- modoboa/frontend_dist/assets/importExport-BlQYb0NO.js +1 -0
- modoboa/frontend_dist/assets/index-DuzUMVLM.js +1 -0
- modoboa/frontend_dist/assets/index-I1VDlN4g.js +984 -0
- modoboa/frontend_dist/assets/layout-D8ZJPiJ_.js +1 -0
- modoboa/frontend_dist/assets/{layout.store-CSmWF8Cf.js → layout.store-DkjrAoXt.js} +1 -1
- modoboa/frontend_dist/assets/logos-q8SEyAa4.js +1 -0
- modoboa/frontend_dist/assets/{logs-DIXCC1C0.js → logs-B7IJ7LBa.js} +1 -1
- modoboa/frontend_dist/assets/{parameters-u6VE5cq8.js → parameters-A6iBEYQq.js} +1 -1
- modoboa/frontend_dist/assets/{parameters.store-D4x8W7LS.js → parameters.store-BiXS4_6w.js} +1 -1
- modoboa/frontend_dist/assets/{permissions-C7XVO62l.js → permissions-CITHLHVg.js} +1 -1
- modoboa/frontend_dist/assets/{ssrBoot-B-x5LB2_.js → ssrBoot-AzTdjPjk.js} +1 -1
- modoboa/frontend_dist/assets/{tag-DCSJ2lri.js → tag-BnSYRTcD.js} +1 -1
- modoboa/frontend_dist/assets/transports-Dz7c6kIy.js +1 -0
- modoboa/frontend_dist/assets/{webmail-D2rioayn.js → webmail-CSH_3l6R.js} +1 -1
- modoboa/frontend_dist/config.json +1 -3
- modoboa/frontend_dist/index.html +1 -1
- modoboa/lib/redis.py +1 -5
- modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ja_JP/LC_MESSAGES/django.po +122 -263
- modoboa/maillog/management/commands/logparser.py +1 -0
- modoboa/pdfcredentials/api/v2/serializers.py +17 -5
- modoboa/pdfcredentials/api/v2/tests.py +28 -0
- modoboa/pdfcredentials/app_settings.py +1 -1
- {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/METADATA +2 -2
- {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/RECORD +150 -147
- modoboa/frontend_dist/assets/AccountLayout-DMwguvdC.js +0 -1
- modoboa/frontend_dist/assets/AccountView-DegeD2uG.js +0 -1
- modoboa/frontend_dist/assets/AdminLayout-CTbzqJSq.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailView-B6Tbw3MS.js +0 -1
- modoboa/frontend_dist/assets/ConnectedLayout-CNzvhsb9.js +0 -1
- modoboa/frontend_dist/assets/CreationForm-ChgnxngD.js +0 -1
- modoboa/frontend_dist/assets/CreationForm-DLumMdUy.css +0 -1
- modoboa/frontend_dist/assets/DashboardView-CubvMqxt.js +0 -1
- modoboa/frontend_dist/assets/DomainAdminList-B0z8fRmf.js +0 -1
- modoboa/frontend_dist/assets/DomainEditView-DrZ9__pt.js +0 -1
- modoboa/frontend_dist/assets/DomainTransportForm-DEsm5zOa.js +0 -1
- modoboa/frontend_dist/assets/DomainView-RmJalGk7.js +0 -5
- modoboa/frontend_dist/assets/DomainsView-DzL9wtff.js +0 -1
- modoboa/frontend_dist/assets/EmptyLayout-FS-cJHKI.js +0 -1
- modoboa/frontend_dist/assets/ForwardEmailView-Lpw9sqB4.js +0 -1
- modoboa/frontend_dist/assets/IdentitiesView-7nHABB9n.js +0 -1
- modoboa/frontend_dist/assets/IdentitiesView-cvIq8q24.css +0 -1
- modoboa/frontend_dist/assets/MessagesView-Dyg8Y4bi.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-Bkw5_CyJ.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-CyAAGtfU.js +0 -1
- modoboa/frontend_dist/assets/ProvidersView-CjKsqSYV.js +0 -1
- modoboa/frontend_dist/assets/ReplyEmailView-DcUkD2HR.js +0 -1
- modoboa/frontend_dist/assets/SettingsView-TStQqZWd.js +0 -6
- modoboa/frontend_dist/assets/UserLayout-C6_Zjp6P.js +0 -1
- modoboa/frontend_dist/assets/VContainer-B8HjqhNU.js +0 -1
- modoboa/frontend_dist/assets/VSheet-C8P69LA8.js +0 -1
- modoboa/frontend_dist/assets/VSpacer-pLqQLLwB.js +0 -1
- modoboa/frontend_dist/assets/WebmailLayout-E3DJFEW2.js +0 -1
- modoboa/frontend_dist/assets/admin-B0fFM9gW.js +0 -1
- modoboa/frontend_dist/assets/importExport-B7mBfHxZ.js +0 -1
- modoboa/frontend_dist/assets/index-BCUdq-Zu.js +0 -852
- modoboa/frontend_dist/assets/index-CJ87ZZG1.js +0 -1
- modoboa/frontend_dist/assets/layout-CLqGxWz4.js +0 -1
- {modoboa-2.4.9.data → modoboa-2.4.11.data}/scripts/modoboa-admin.py +0 -0
- {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/WHEEL +0 -0
- {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/entry_points.txt +0 -0
- {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/licenses/LICENSE +0 -0
- {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/top_level.txt +0 -0
|
@@ -68,12 +68,18 @@ class DomainSerializer(serializers.ModelSerializer):
|
|
|
68
68
|
"message_limit",
|
|
69
69
|
"creation",
|
|
70
70
|
"last_modification",
|
|
71
|
+
"allocated_quota_in_percent",
|
|
72
|
+
"mailbox_count",
|
|
73
|
+
"mbalias_count",
|
|
74
|
+
"domainalias_count",
|
|
75
|
+
"enable_dns_checks",
|
|
71
76
|
)
|
|
72
77
|
read_only_fields = (
|
|
73
78
|
"pk",
|
|
74
79
|
"dkim_public_key",
|
|
75
80
|
"dns_global_status",
|
|
76
|
-
"allocated_quota_in_percent"
|
|
81
|
+
"allocated_quota_in_percent",
|
|
82
|
+
"mailbox_count",
|
|
77
83
|
"mbalias_count",
|
|
78
84
|
"domainalias_count",
|
|
79
85
|
"enable_dns_checks",
|
|
@@ -202,6 +208,7 @@ class MailboxSerializer(serializers.ModelSerializer):
|
|
|
202
208
|
"message_limit",
|
|
203
209
|
"quota_usage",
|
|
204
210
|
"is_send_only",
|
|
211
|
+
"sent_messages_in_percent",
|
|
205
212
|
)
|
|
206
213
|
|
|
207
214
|
def get_quota_usage(self, mb) -> int:
|
|
@@ -61,6 +61,8 @@ class DomainSerializer(v1_serializers.DomainSerializer):
|
|
|
61
61
|
"domain_admin",
|
|
62
62
|
"transport",
|
|
63
63
|
"opened_alarms_count",
|
|
64
|
+
"sent_messages",
|
|
65
|
+
"sent_messages_in_percent",
|
|
64
66
|
)
|
|
65
67
|
|
|
66
68
|
def __init__(self, *args, **kwargs):
|
|
@@ -221,9 +223,9 @@ class AdminGlobalParametersSerializer(serializers.Serializer):
|
|
|
221
223
|
enable_dkim_checks = serializers.BooleanField(default=True)
|
|
222
224
|
enable_dmarc_checks = serializers.BooleanField(default=True)
|
|
223
225
|
enable_autoconfig_checks = serializers.BooleanField(default=True)
|
|
224
|
-
custom_dns_server = serializers.IPAddressField(allow_blank=True)
|
|
226
|
+
custom_dns_server = serializers.IPAddressField(allow_blank=True, allow_null=True)
|
|
225
227
|
enable_dnsbl_checks = serializers.BooleanField(default=True)
|
|
226
|
-
dkim_keys_storage_dir = serializers.CharField(allow_blank=True)
|
|
228
|
+
dkim_keys_storage_dir = serializers.CharField(allow_blank=True, allow_null=True)
|
|
227
229
|
dkim_default_key_length = serializers.ChoiceField(
|
|
228
230
|
default=2048, choices=constants.DKIM_KEY_LENGTHS
|
|
229
231
|
)
|
|
@@ -729,7 +731,7 @@ class CSVImportSerializer(serializers.Serializer):
|
|
|
729
731
|
class CSVIdentityImportSerializer(CSVImportSerializer):
|
|
730
732
|
"""Custom serializer for identity import."""
|
|
731
733
|
|
|
732
|
-
|
|
734
|
+
crypt_passwords = serializers.BooleanField()
|
|
733
735
|
|
|
734
736
|
|
|
735
737
|
class AlarmSerializer(serializers.ModelSerializer):
|
modoboa/admin/api/v2/tests.py
CHANGED
|
@@ -331,6 +331,16 @@ class AccountViewSetTestCase(ModoAPITestCase):
|
|
|
331
331
|
resp = self.client.patch(url, data, format="json")
|
|
332
332
|
self.assertEqual(resp.status_code, 200)
|
|
333
333
|
|
|
334
|
+
def test_update_quota(self):
|
|
335
|
+
account = core_models.User.objects.get(username="user@test.com")
|
|
336
|
+
url = reverse("v2:account-detail", args=[account.pk])
|
|
337
|
+
data = {
|
|
338
|
+
"mailbox": {"quota": 1},
|
|
339
|
+
}
|
|
340
|
+
resp = self.client.patch(url, data, format="json")
|
|
341
|
+
self.assertEqual(resp.status_code, 200)
|
|
342
|
+
self.assertEqual(account.mailbox.quota, data["mailbox"]["quota"])
|
|
343
|
+
|
|
334
344
|
def test_user_updates_password(self):
|
|
335
345
|
account = core_models.User.objects.get(username="user@test.com")
|
|
336
346
|
self.authenticate_user(account)
|
|
@@ -583,7 +593,7 @@ dlist; dlist@test.com; True; user1@test.com; user@extdomain.com
|
|
|
583
593
|
) # NOQA:E501
|
|
584
594
|
self.client.post(
|
|
585
595
|
reverse("v2:identities-import-from-csv"),
|
|
586
|
-
{"sourcefile": f, "
|
|
596
|
+
{"sourcefile": f, "crypt_passwords": True},
|
|
587
597
|
)
|
|
588
598
|
admin = core_models.User.objects.get(username="admin")
|
|
589
599
|
u1 = core_models.User.objects.get(username="user1@test.com")
|
modoboa/admin/lib.py
CHANGED
|
@@ -138,7 +138,7 @@ def import_domainalias(user, row, formopts):
|
|
|
138
138
|
def import_account(user, row, formopts):
|
|
139
139
|
"""Specific code for accounts import"""
|
|
140
140
|
account = User()
|
|
141
|
-
account.from_csv(user, row, formopts["
|
|
141
|
+
account.from_csv(user, row, formopts["crypt_passwords"])
|
|
142
142
|
|
|
143
143
|
|
|
144
144
|
def _import_alias(user, row, **kwargs):
|
modoboa/admin/models/mailbox.py
CHANGED
|
@@ -295,19 +295,21 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject):
|
|
|
295
295
|
def update_from_dict(self, user, values):
|
|
296
296
|
"""Update mailbox from a dictionary."""
|
|
297
297
|
newaddress = None
|
|
298
|
-
if
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
298
|
+
if "email" in values:
|
|
299
|
+
if values["email"] != self.full_address:
|
|
300
|
+
newaddress = values["email"]
|
|
301
|
+
elif (
|
|
302
|
+
self.user.role == "SimpleUsers"
|
|
303
|
+
and self.user.username != self.full_address
|
|
304
|
+
):
|
|
305
|
+
newaddress = self.user.username
|
|
306
|
+
if newaddress is not None:
|
|
307
|
+
local_part, domname = split_mailbox(newaddress)
|
|
308
|
+
domain = Domain.objects.filter(name=domname).first()
|
|
309
|
+
if domain is None:
|
|
310
|
+
raise lib_exceptions.NotFound(_("Domain does not exist"))
|
|
311
|
+
if not user.can_access(domain):
|
|
312
|
+
raise lib_exceptions.PermDeniedException
|
|
311
313
|
if "use_domain_quota" in values:
|
|
312
314
|
self.use_domain_quota = values["use_domain_quota"]
|
|
313
315
|
if "use_domain_quota" in values or "quota" in values:
|
modoboa/admin/models/mxrecord.py
CHANGED
|
@@ -71,6 +71,10 @@ class MXRecord(models.Model):
|
|
|
71
71
|
def __str__(self):
|
|
72
72
|
return f"{self.name} ({self.address}) for {self.domain} "
|
|
73
73
|
|
|
74
|
+
@property
|
|
75
|
+
def active_dnsbl_results(self):
|
|
76
|
+
return self.dnsblresult_set.exclude(status="")
|
|
77
|
+
|
|
74
78
|
|
|
75
79
|
class DNSBLQuerySet(models.QuerySet):
|
|
76
80
|
"""Custom manager for DNSBLResultManager."""
|
|
@@ -88,7 +88,7 @@ account; user1@nonlocal.com; toto; User; One; True; SimpleUsers; user1@nonlocal.
|
|
|
88
88
|
name="identities.csv",
|
|
89
89
|
) # NOQA:E501
|
|
90
90
|
url = reverse("v2:identities-import-from-csv")
|
|
91
|
-
self.client.post(url, {"sourcefile": f, "
|
|
91
|
+
self.client.post(url, {"sourcefile": f, "crypt_passwords": True})
|
|
92
92
|
self.assertFalse(User.objects.filter(username="user1@nonlocal.com").exists())
|
|
93
93
|
|
|
94
94
|
def test_import_invalid_quota(self):
|
|
@@ -99,7 +99,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; ; t
|
|
|
99
99
|
name="identities.csv",
|
|
100
100
|
) # NOQA:E501
|
|
101
101
|
url = reverse("v2:identities-import-from-csv")
|
|
102
|
-
resp = self.client.post(url, {"sourcefile": f, "
|
|
102
|
+
resp = self.client.post(url, {"sourcefile": f, "crypt_passwords": True})
|
|
103
103
|
self.assertIn("wrong quota value", resp.content.decode())
|
|
104
104
|
|
|
105
105
|
def test_import_domain_by_domainadmin(self):
|
|
@@ -114,7 +114,7 @@ domain; domain2.com; 1000; 200; False
|
|
|
114
114
|
name="identities.csv",
|
|
115
115
|
)
|
|
116
116
|
url = reverse("v2:identities-import-from-csv")
|
|
117
|
-
resp = self.client.post(url, {"sourcefile": f, "
|
|
117
|
+
resp = self.client.post(url, {"sourcefile": f, "crypt_passwords": True})
|
|
118
118
|
self.assertIn("You are not allowed to import domains", resp.content.decode())
|
|
119
119
|
f = ContentFile(
|
|
120
120
|
b"""
|
|
@@ -122,7 +122,7 @@ domainalias; domalias1.com; test.com; True
|
|
|
122
122
|
""",
|
|
123
123
|
name="identities.csv",
|
|
124
124
|
)
|
|
125
|
-
resp = self.client.post(url, {"sourcefile": f, "
|
|
125
|
+
resp = self.client.post(url, {"sourcefile": f, "crypt_passwords": True})
|
|
126
126
|
self.assertIn(
|
|
127
127
|
"You are not allowed to import domain aliases", resp.content.decode()
|
|
128
128
|
)
|
|
@@ -138,7 +138,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 40
|
|
|
138
138
|
)
|
|
139
139
|
resp = self.client.post(
|
|
140
140
|
reverse("v2:identities-import-from-csv"),
|
|
141
|
-
{"sourcefile": f, "
|
|
141
|
+
{"sourcefile": f, "crypt_passwords": True},
|
|
142
142
|
)
|
|
143
143
|
self.assertIn("test.com: domain quota exceeded", resp.content.decode())
|
|
144
144
|
|
|
@@ -151,7 +151,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com
|
|
|
151
151
|
)
|
|
152
152
|
self.client.post(
|
|
153
153
|
reverse("v2:identities-import-from-csv"),
|
|
154
|
-
{"sourcefile": f, "
|
|
154
|
+
{"sourcefile": f, "crypt_passwords": True},
|
|
155
155
|
)
|
|
156
156
|
account = User.objects.get(username="user1@test.com")
|
|
157
157
|
self.assertEqual(
|
|
@@ -168,7 +168,7 @@ account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0;
|
|
|
168
168
|
) # NOQA:E501
|
|
169
169
|
self.client.post(
|
|
170
170
|
reverse("v2:identities-import-from-csv"),
|
|
171
|
-
{"sourcefile": f, "
|
|
171
|
+
{"sourcefile": f, "crypt_passwords": True, "continue_if_exists": True},
|
|
172
172
|
)
|
|
173
173
|
admin = User.objects.get(username="admin")
|
|
174
174
|
u1 = User.objects.get(username="truc@test.com")
|
|
@@ -189,7 +189,7 @@ account; sa@test.com; toto; Super; Admin; True; SuperAdmins; superadmin@test.com
|
|
|
189
189
|
) # NOQA:E501
|
|
190
190
|
self.client.post(
|
|
191
191
|
reverse("v2:identities-import-from-csv"),
|
|
192
|
-
{"sourcefile": f, "
|
|
192
|
+
{"sourcefile": f, "crypt_passwords": True, "continue_if_exists": True},
|
|
193
193
|
)
|
|
194
194
|
with self.assertRaises(User.DoesNotExist):
|
|
195
195
|
User.objects.get(username="sa@test.com")
|
|
@@ -203,7 +203,7 @@ alias;user.alias@test.com;True;user@test.com;;;;;;;;;;;;;;;;
|
|
|
203
203
|
)
|
|
204
204
|
self.client.post(
|
|
205
205
|
reverse("v2:identities-import-from-csv"),
|
|
206
|
-
{"sourcefile": f, "
|
|
206
|
+
{"sourcefile": f, "crypt_passwords": True, "continue_if_exists": True},
|
|
207
207
|
)
|
|
208
208
|
alias = Alias.objects.get(address="user.alias@test.com")
|
|
209
209
|
self.assertEqual(alias.type, "alias")
|
|
@@ -218,7 +218,7 @@ alias;user@test.com;True;admin@test.com
|
|
|
218
218
|
)
|
|
219
219
|
self.client.post(
|
|
220
220
|
reverse("v2:identities-import-from-csv"),
|
|
221
|
-
{"sourcefile": f, "
|
|
221
|
+
{"sourcefile": f, "crypt_passwords": True},
|
|
222
222
|
)
|
|
223
223
|
self.assertTrue(
|
|
224
224
|
Alias.objects.filter(address="user@test.com", internal=False).exists()
|
|
@@ -315,7 +315,7 @@ alias; alias1@test.com; True; user1@test.com
|
|
|
315
315
|
) # NOQA:E501
|
|
316
316
|
resp = self.client.post(
|
|
317
317
|
reverse("v2:identities-import-from-csv"),
|
|
318
|
-
{"sourcefile": f, "
|
|
318
|
+
{"sourcefile": f, "crypt_passwords": True},
|
|
319
319
|
)
|
|
320
320
|
self.assertEqual(resp.status_code, 200)
|
|
321
321
|
|
|
@@ -76,8 +76,12 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
76
76
|
default_password = serializers.CharField(default="ChangeMe1!")
|
|
77
77
|
random_password_length = serializers.IntegerField(min_value=8, default=8)
|
|
78
78
|
allow_special_characters = serializers.BooleanField(default=False)
|
|
79
|
-
update_password_url = serializers.URLField(
|
|
80
|
-
|
|
79
|
+
update_password_url = serializers.URLField(
|
|
80
|
+
required=False, allow_blank=True, allow_null=True
|
|
81
|
+
)
|
|
82
|
+
password_recovery_msg = serializers.CharField(
|
|
83
|
+
required=False, allow_blank=True, allow_null=True
|
|
84
|
+
)
|
|
81
85
|
sms_password_recovery = serializers.BooleanField(default=False)
|
|
82
86
|
sms_provider = serializers.ChoiceField(
|
|
83
87
|
choices=constants.SMS_BACKENDS, required=False, allow_null=True
|
|
@@ -87,7 +91,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
87
91
|
ldap_server_address = serializers.CharField(default="localhost")
|
|
88
92
|
ldap_server_port = serializers.IntegerField(default=389)
|
|
89
93
|
ldap_enable_secondary_server = serializers.BooleanField(default=False)
|
|
90
|
-
ldap_secondary_server_address = serializers.CharField(
|
|
94
|
+
ldap_secondary_server_address = serializers.CharField(
|
|
95
|
+
required=False, allow_blank=True, allow_null=True
|
|
96
|
+
)
|
|
91
97
|
ldap_secondary_server_port = serializers.IntegerField(default=389, required=False)
|
|
92
98
|
ldap_secured = serializers.ChoiceField(
|
|
93
99
|
choices=constants.LDAP_SECURE_MODES, default="none"
|
|
@@ -109,9 +115,11 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
109
115
|
choices=constants.LDAP_AUTH_METHODS,
|
|
110
116
|
default="searchbind",
|
|
111
117
|
)
|
|
112
|
-
ldap_bind_dn = serializers.CharField(
|
|
118
|
+
ldap_bind_dn = serializers.CharField(
|
|
119
|
+
default="", required=False, allow_blank=True, allow_null=True
|
|
120
|
+
)
|
|
113
121
|
ldap_bind_password = serializers.CharField(
|
|
114
|
-
default="", required=False, allow_blank=True
|
|
122
|
+
default="", required=False, allow_blank=True, allow_null=True
|
|
115
123
|
)
|
|
116
124
|
ldap_search_base = serializers.CharField(
|
|
117
125
|
default="", required=False, allow_blank=True
|
|
@@ -124,15 +132,21 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
124
132
|
)
|
|
125
133
|
|
|
126
134
|
# LDAP sync settings
|
|
127
|
-
ldap_sync_bind_dn = serializers.CharField(
|
|
128
|
-
|
|
135
|
+
ldap_sync_bind_dn = serializers.CharField(
|
|
136
|
+
required=False, allow_blank=True, allow_null=True
|
|
137
|
+
)
|
|
138
|
+
ldap_sync_bind_password = serializers.CharField(
|
|
139
|
+
required=False, allow_blank=True, allow_null=True
|
|
140
|
+
)
|
|
129
141
|
ldap_enable_sync = serializers.BooleanField(default=False)
|
|
130
142
|
ldap_sync_delete_remote_account = serializers.BooleanField(default=False)
|
|
131
143
|
ldap_sync_account_dn_template = serializers.CharField(
|
|
132
|
-
required=False, allow_blank=True
|
|
144
|
+
required=False, allow_blank=True, allow_null=True
|
|
133
145
|
)
|
|
134
146
|
ldap_enable_import = serializers.BooleanField(default=False)
|
|
135
|
-
ldap_import_search_base = serializers.CharField(
|
|
147
|
+
ldap_import_search_base = serializers.CharField(
|
|
148
|
+
required=False, allow_blank=True, allow_null=True
|
|
149
|
+
)
|
|
136
150
|
ldap_import_search_filter = serializers.CharField(default="(cn=*)", required=False)
|
|
137
151
|
ldap_import_username_attr = serializers.CharField(default="cn")
|
|
138
152
|
ldap_dovecot_sync = serializers.BooleanField(default=False)
|
|
@@ -141,7 +155,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
141
155
|
)
|
|
142
156
|
|
|
143
157
|
# Dashboard settings
|
|
144
|
-
custom_welcome_message = serializers.CharField(
|
|
158
|
+
custom_welcome_message = serializers.CharField(
|
|
159
|
+
required=False, allow_blank=True, allow_null=True
|
|
160
|
+
)
|
|
145
161
|
rss_feed_url = serializers.URLField(
|
|
146
162
|
allow_blank=True, required=False, allow_null=True
|
|
147
163
|
)
|
|
@@ -162,7 +178,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
162
178
|
enable_api_communication = serializers.BooleanField(default=True)
|
|
163
179
|
check_new_versions = serializers.BooleanField(default=True)
|
|
164
180
|
send_new_versions_email = serializers.BooleanField(default=False)
|
|
165
|
-
new_versions_email_rcpt = lib_fields.DRFEmailFieldUTF8(
|
|
181
|
+
new_versions_email_rcpt = lib_fields.DRFEmailFieldUTF8(
|
|
182
|
+
required=False, allow_null=True
|
|
183
|
+
)
|
|
166
184
|
send_statistics = serializers.BooleanField(default=True)
|
|
167
185
|
|
|
168
186
|
# Misc settings
|
|
@@ -170,6 +188,7 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
170
188
|
inactive_account_threshold = serializers.IntegerField(default=30)
|
|
171
189
|
top_notifications_check_interval = serializers.IntegerField(default=30)
|
|
172
190
|
log_maximum_age = serializers.IntegerField(default=365)
|
|
191
|
+
message_history_maximum_age = serializers.IntegerField(default=180)
|
|
173
192
|
items_per_page = serializers.IntegerField(default=30)
|
|
174
193
|
default_top_redirection = serializers.ChoiceField(
|
|
175
194
|
default="user", choices=[("user", _("User profile"))], required=False
|
|
@@ -195,10 +214,11 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
195
214
|
return value
|
|
196
215
|
|
|
197
216
|
def validate_ldap_sync_account_dn_template(self, value):
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
217
|
+
if value:
|
|
218
|
+
try:
|
|
219
|
+
value % {"user": "toto"}
|
|
220
|
+
except (KeyError, ValueError):
|
|
221
|
+
raise serializers.ValidationError(_("Invalid syntax")) from None
|
|
202
222
|
return value
|
|
203
223
|
|
|
204
224
|
def validate_ldap_search_filter(self, value):
|
|
@@ -672,3 +692,11 @@ class ThemeSerializer(serializers.Serializer):
|
|
|
672
692
|
theme_primary_color_light = serializers.CharField(default="#3688F9")
|
|
673
693
|
theme_secondary_color = serializers.CharField(default="#F18429")
|
|
674
694
|
theme_label_color = serializers.CharField(default="#616161")
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
class StatisticsSerializer(serializers.Serializer):
|
|
698
|
+
|
|
699
|
+
domain_count = serializers.IntegerField()
|
|
700
|
+
domain_alias_count = serializers.IntegerField()
|
|
701
|
+
account_count = serializers.IntegerField()
|
|
702
|
+
alias_count = serializers.IntegerField()
|
modoboa/core/api/v2/tests.py
CHANGED
|
@@ -658,3 +658,21 @@ class NewsFeedAPIViewTestCase(ModoAPITestCase):
|
|
|
658
658
|
response = self.client.get(url)
|
|
659
659
|
self.assertEqual(response.status_code, 200)
|
|
660
660
|
self.assertIn("modoboa", response.json()[0]["link"])
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
class StatisticsAPIViewTestCase(ModoAPITestCase):
|
|
664
|
+
|
|
665
|
+
@classmethod
|
|
666
|
+
def setUpTestData(cls):
|
|
667
|
+
"""Create test data."""
|
|
668
|
+
super().setUpTestData()
|
|
669
|
+
factories.populate_database()
|
|
670
|
+
|
|
671
|
+
def test_get_statistics(self):
|
|
672
|
+
url = reverse("v2:statistics")
|
|
673
|
+
response = self.client.get(url)
|
|
674
|
+
self.assertEqual(response.status_code, 200)
|
|
675
|
+
stats = response.json()
|
|
676
|
+
self.assertEqual(stats["domain_count"], 2)
|
|
677
|
+
self.assertEqual(stats["account_count"], 5)
|
|
678
|
+
self.assertEqual(stats["alias_count"], 3)
|
modoboa/core/api/v2/urls.py
CHANGED
|
@@ -42,6 +42,11 @@ urlpatterns += [
|
|
|
42
42
|
views.NewsFeedAPIView.as_view(),
|
|
43
43
|
name="news-feed",
|
|
44
44
|
),
|
|
45
|
+
path(
|
|
46
|
+
"admin/statistics/",
|
|
47
|
+
views.StatisticsAPIView.as_view(),
|
|
48
|
+
name="statistics",
|
|
49
|
+
),
|
|
45
50
|
path("capabilities/", views.CapabilitiesAPIView.as_view(), name="capabilities"),
|
|
46
51
|
path("theme/", views.ThemeAPIView.as_view(), name="theme"),
|
|
47
52
|
]
|
modoboa/core/api/v2/views.py
CHANGED
|
@@ -14,7 +14,8 @@ from drf_spectacular.utils import extend_schema
|
|
|
14
14
|
from rest_framework import permissions, response
|
|
15
15
|
from rest_framework.views import APIView
|
|
16
16
|
|
|
17
|
-
from modoboa.
|
|
17
|
+
from modoboa.admin import models as admin_models
|
|
18
|
+
from modoboa.core import models, signals
|
|
18
19
|
from modoboa.core.utils import check_for_updates, get_capabilities
|
|
19
20
|
from modoboa.lib.permissions import IsSuperUser, IsPrivilegedUser
|
|
20
21
|
from modoboa.lib.throttle import (
|
|
@@ -226,3 +227,21 @@ class NewsFeedAPIView(APIView):
|
|
|
226
227
|
|
|
227
228
|
serializer = serializers.NewsFeedEntrySerializer(entries, many=True)
|
|
228
229
|
return response.Response(serializer.data)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class StatisticsAPIView(APIView):
|
|
233
|
+
"""Return some statistics about this modoboa instance."""
|
|
234
|
+
|
|
235
|
+
permission_classes = [permissions.IsAuthenticated, IsSuperUser]
|
|
236
|
+
throttle_classes = [UserLesserDdosUser]
|
|
237
|
+
|
|
238
|
+
@extend_schema(responses=serializers.StatisticsSerializer())
|
|
239
|
+
def get(self, request, *args, **kwargs):
|
|
240
|
+
data = {
|
|
241
|
+
"domain_count": admin_models.Domain.objects.count(),
|
|
242
|
+
"domain_alias_count": admin_models.DomainAlias.objects.count(),
|
|
243
|
+
"account_count": models.User.objects.count(),
|
|
244
|
+
"alias_count": admin_models.Alias.objects.filter(internal=False).count(),
|
|
245
|
+
}
|
|
246
|
+
serializer = serializers.StatisticsSerializer(data)
|
|
247
|
+
return response.Response(serializer.data)
|
modoboa/core/app_settings.py
CHANGED
|
@@ -649,7 +649,7 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
|
|
|
649
649
|
"new_versions_email_rcpt",
|
|
650
650
|
{
|
|
651
651
|
"label": gettext_lazy("Recipient"),
|
|
652
|
-
"display": "
|
|
652
|
+
"display": "send_new_versions_email=true",
|
|
653
653
|
"help_text": gettext_lazy(
|
|
654
654
|
"Recipient of new versions notification emails."
|
|
655
655
|
),
|
|
@@ -720,6 +720,17 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
|
|
|
720
720
|
),
|
|
721
721
|
},
|
|
722
722
|
),
|
|
723
|
+
(
|
|
724
|
+
"message_history_maximum_age",
|
|
725
|
+
{
|
|
726
|
+
"label": gettext_lazy(
|
|
727
|
+
"Retention time in message history"
|
|
728
|
+
),
|
|
729
|
+
"help_text": gettext_lazy(
|
|
730
|
+
"Retention time (in days) of a message in the message history section"
|
|
731
|
+
),
|
|
732
|
+
},
|
|
733
|
+
),
|
|
723
734
|
(
|
|
724
735
|
"items_per_page",
|
|
725
736
|
{
|
modoboa/core/fido2_auth.py
CHANGED
|
@@ -34,10 +34,9 @@ def begin_registration(request):
|
|
|
34
34
|
),
|
|
35
35
|
list(get_creds_from_user(request.user.pk).values()),
|
|
36
36
|
user_verification=UserVerificationRequirement.DISCOURAGED,
|
|
37
|
-
extensions={"credentialProtectionPolicy": "userVerificationOptional"},
|
|
38
37
|
)
|
|
39
38
|
request.session["fido2_state"] = state
|
|
40
|
-
return options
|
|
39
|
+
return dict(options)
|
|
41
40
|
|
|
42
41
|
|
|
43
42
|
def end_registration(request):
|
|
@@ -5,6 +5,7 @@ from django.core.management.base import BaseCommand
|
|
|
5
5
|
from django.utils import timezone
|
|
6
6
|
|
|
7
7
|
from modoboa.core.models import Log
|
|
8
|
+
from modoboa.maillog.models import Maillog
|
|
8
9
|
from modoboa.parameters import tools as param_tools
|
|
9
10
|
|
|
10
11
|
|
|
@@ -41,4 +42,12 @@ class Command(BaseCommand):
|
|
|
41
42
|
self.__vprint(f"Deleting logs older than {log_maximum_age} days...")
|
|
42
43
|
limit = timezone.now() - datetime.timedelta(log_maximum_age)
|
|
43
44
|
Log.objects.filter(date_created__lt=limit).delete()
|
|
45
|
+
message_history_maximum_age = param_tools.get_global_parameter(
|
|
46
|
+
"message_history_maximum_age"
|
|
47
|
+
)
|
|
48
|
+
self.__vprint(
|
|
49
|
+
f"Deleting messages in history older than {message_history_maximum_age} days..."
|
|
50
|
+
)
|
|
51
|
+
limit = timezone.now() - datetime.timedelta(message_history_maximum_age)
|
|
52
|
+
Maillog.objects.filter(date__lt=limit).delete()
|
|
44
53
|
self.__vprint("Done.")
|
|
@@ -159,12 +159,6 @@ class Command(BaseCommand):
|
|
|
159
159
|
os.path.dirname(__file__), "../../../frontend_dist/"
|
|
160
160
|
)
|
|
161
161
|
frontend_target_dir = f"{settings.BASE_DIR}/frontend"
|
|
162
|
-
if hasattr(settings, "MODOBOA_CUSTOM_LOGO"):
|
|
163
|
-
menu_logo_path = settings.MODOBOA_CUSTOM_LOGO
|
|
164
|
-
form_logo_path = settings.MODOBOA_CUSTOM_LOGO
|
|
165
|
-
else:
|
|
166
|
-
menu_logo_path = f"{frontend_target_dir}/assets/Modoboa_RVB-BLANC-SANS.png"
|
|
167
|
-
form_logo_path = f"{frontend_target_dir}/assets/Modoboa_RVB-BLEU-SANS.png"
|
|
168
162
|
if os.path.isdir(base_frontend_dir):
|
|
169
163
|
shutil.rmtree(frontend_target_dir, ignore_errors=True)
|
|
170
164
|
os.makedirs(frontend_target_dir, exist_ok=True)
|
|
@@ -178,13 +172,13 @@ class Command(BaseCommand):
|
|
|
178
172
|
api_base_url = "/api/v2"
|
|
179
173
|
api_doc_url = "/api/schema-v2/swagger/"
|
|
180
174
|
oauth_authority_url = "/api/o"
|
|
181
|
-
if options["relative_urls_in_config"]:
|
|
182
|
-
api_base_url
|
|
183
|
-
api_doc_url
|
|
184
|
-
oauth_authority_url
|
|
185
|
-
oauth_post_logout_redirect_uri = ""
|
|
186
|
-
else:
|
|
175
|
+
if not options["relative_urls_in_config"]:
|
|
176
|
+
api_base_url = f"{base_uri}{api_base_url}"
|
|
177
|
+
api_doc_url = f"{base_uri}{base_uri}"
|
|
178
|
+
oauth_authority_url = f"{base_uri}{oauth_authority_url}"
|
|
187
179
|
oauth_post_logout_redirect_uri = base_uri
|
|
180
|
+
else:
|
|
181
|
+
oauth_post_logout_redirect_uri = ""
|
|
188
182
|
|
|
189
183
|
with open(f"{frontend_target_dir}/config.json", "w") as fp:
|
|
190
184
|
fp.write(
|
|
@@ -194,9 +188,7 @@ class Command(BaseCommand):
|
|
|
194
188
|
"OAUTH_AUTHORITY_URL": "{oauth_authority_url}",
|
|
195
189
|
"OAUTH_CLIENT_ID": "{client_id}",
|
|
196
190
|
"OAUTH_REDIRECT_URI": "{redirect_uri}",
|
|
197
|
-
"OAUTH_POST_REDIRECT_URI": "{oauth_post_logout_redirect_uri}"
|
|
198
|
-
"MENU_LOGO_PATH": "{menu_logo_path}",
|
|
199
|
-
"CREATION_FORM_LOGO_PATH": "{form_logo_path}"
|
|
191
|
+
"OAUTH_POST_REDIRECT_URI": "{oauth_post_logout_redirect_uri}"
|
|
200
192
|
}}
|
|
201
193
|
"""
|
|
202
194
|
)
|
|
@@ -9,9 +9,9 @@ class Migration(migrations.Migration):
|
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
operations = [
|
|
12
|
-
migrations.RenameIndex(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
),
|
|
12
|
+
# migrations.RenameIndex(
|
|
13
|
+
# model_name="user",
|
|
14
|
+
# new_name="core_user_email_c0c03f_idx",
|
|
15
|
+
# old_fields=("email", "is_active"),
|
|
16
|
+
# ),
|
|
17
17
|
]
|
modoboa/core/tests/test_core.py
CHANGED
|
@@ -16,6 +16,7 @@ from django.urls import reverse
|
|
|
16
16
|
from django.utils import timezone
|
|
17
17
|
|
|
18
18
|
from modoboa.lib.tests import ModoTestCase, SimpleModoTestCase
|
|
19
|
+
from modoboa.maillog import factories as maillog_factories, models as maillog_models
|
|
19
20
|
from .. import factories, mocks, models
|
|
20
21
|
|
|
21
22
|
|
|
@@ -60,12 +61,16 @@ class ManagementCommandsTestCase(SimpleModoTestCase):
|
|
|
60
61
|
|
|
61
62
|
def test_clean_logs(self):
|
|
62
63
|
"""Run cleanlogs command."""
|
|
64
|
+
management.call_command("load_initial_data", "--no-frontend")
|
|
63
65
|
log1 = factories.LogFactory()
|
|
64
66
|
factories.LogFactory()
|
|
65
67
|
log1.date_created -= relativedelta(years=2)
|
|
66
68
|
log1.save(update_fields=["date_created"])
|
|
69
|
+
maillog_factories.MaillogFactory(date=timezone.now() - relativedelta(days=190))
|
|
70
|
+
maillog_factories.MaillogFactory()
|
|
67
71
|
management.call_command("cleanlogs")
|
|
68
72
|
self.assertEqual(models.Log.objects.count(), 1)
|
|
73
|
+
self.assertEqual(maillog_models.Maillog.objects.count(), 1)
|
|
69
74
|
|
|
70
75
|
def test_init_data_non_duplicate_client_creation(self):
|
|
71
76
|
"""Test that the load_initial_data command does not create duplicates
|
modoboa/core/utils.py
CHANGED
|
@@ -111,4 +111,7 @@ def get_capabilities():
|
|
|
111
111
|
if is_rspamd_installed:
|
|
112
112
|
rspamd_options = get_rspamd_options()
|
|
113
113
|
capabilities.update({"rspamd": rspamd_options})
|
|
114
|
+
# IMAP migration
|
|
115
|
+
if "modoboa.imap_migration" in settings.MODOBOA_APPS:
|
|
116
|
+
capabilities.update({"imap_migration": {}})
|
|
114
117
|
return capabilities
|
|
@@ -7,22 +7,22 @@ from modoboa.admin import models as admin_models
|
|
|
7
7
|
from ... import models
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
11
|
-
"""Serializer for
|
|
10
|
+
class DNSBLResultSerializer(serializers.ModelSerializer):
|
|
11
|
+
"""Serializer for DNSBLResult."""
|
|
12
12
|
|
|
13
13
|
class Meta:
|
|
14
|
-
model = admin_models.
|
|
15
|
-
fields = ("
|
|
14
|
+
model = admin_models.DNSBLResult
|
|
15
|
+
fields = ("provider", "status")
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class
|
|
19
|
-
"""Serializer for
|
|
18
|
+
class MXRecordSerializer(serializers.ModelSerializer):
|
|
19
|
+
"""Serializer for MXRecord."""
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
dnsbl_results = DNSBLResultSerializer(many=True, source="active_dnsbl_results")
|
|
22
22
|
|
|
23
23
|
class Meta:
|
|
24
|
-
model = admin_models.
|
|
25
|
-
fields = ("
|
|
24
|
+
model = admin_models.MXRecord
|
|
25
|
+
fields = ("name", "address", "dnsbl_results", "updated")
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class DNSRecordSerializer(serializers.ModelSerializer):
|
|
@@ -41,13 +41,11 @@ class DNSDetailSerializer(serializers.ModelSerializer):
|
|
|
41
41
|
spf_record = DNSRecordSerializer()
|
|
42
42
|
dkim_record = DNSRecordSerializer()
|
|
43
43
|
dmarc_record = DNSRecordSerializer()
|
|
44
|
-
dnsbl_results = DNSBLResultSerializer(many=True, source="dnsblresult_set")
|
|
45
44
|
|
|
46
45
|
class Meta:
|
|
47
46
|
model = admin_models.Domain
|
|
48
47
|
fields = (
|
|
49
48
|
"mx_records",
|
|
50
|
-
"dnsbl_results",
|
|
51
49
|
"autoconfig_record",
|
|
52
50
|
"autodiscover_record",
|
|
53
51
|
"spf_record",
|