modoboa 2.5.1__py3-none-any.whl → 2.6.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/v2/serializers.py +1 -1
- modoboa/admin/models/domain.py +1 -1
- modoboa/admin/models/mixins.py +3 -1
- modoboa/autoconfig/__init__.py +0 -0
- modoboa/autoconfig/apps.py +6 -0
- modoboa/autoconfig/templates/autoconfig/autoconfig.xml +24 -0
- modoboa/autoconfig/templates/autoconfig/autodiscover.xml +27 -0
- modoboa/autoconfig/tests.py +38 -0
- modoboa/autoconfig/urls.py +21 -0
- modoboa/autoconfig/views.py +109 -0
- modoboa/autoreply/api/v2/tests.py +4 -4
- modoboa/calendars/tests.py +8 -8
- modoboa/contacts/tests.py +5 -7
- modoboa/core/api/v1/tests.py +2 -2
- modoboa/core/commands/templates/settings.py.tpl +14 -1
- modoboa/core/models.py +1 -1
- modoboa/core/tests/test_ldap.py +2 -0
- modoboa/core/views/auth.py +27 -8
- modoboa/frontend_dist/assets/{AccountAliasForm-C0oHHyZL.js → AccountAliasForm-Bu1I8sLg.js} +1 -1
- modoboa/frontend_dist/assets/{AccountEditView-lgSJ2Se8.js → AccountEditView-YOpDW-Ob.js} +1 -1
- modoboa/frontend_dist/assets/AccountLayout-Cf_FVfvJ.js +1 -0
- modoboa/frontend_dist/assets/{AccountPasswordSubForm-YsaE_cDx.js → AccountPasswordSubForm-B9X5wAXp.js} +1 -1
- modoboa/frontend_dist/assets/{AccountView-1jfKFDwb.js → AccountView-DCqbD7oi.js} +1 -1
- modoboa/frontend_dist/assets/{AddressBook-CwN64Zls.js → AddressBook-B53LXIsD.js} +1 -1
- modoboa/frontend_dist/assets/AdminLayout-DX-RXYiU.js +1 -0
- modoboa/frontend_dist/assets/AlarmsView-C0KvogOj.js +1 -0
- modoboa/frontend_dist/assets/AliasEditView-BiMPygn9.js +1 -0
- modoboa/frontend_dist/assets/AliasEditView-CYDSiX8l.css +1 -0
- modoboa/frontend_dist/assets/AliasRecipientForm-BhQfATiS.js +1 -0
- modoboa/frontend_dist/assets/{AliasView-DMzA10eD.js → AliasView-eIct4lMV.js} +1 -1
- modoboa/frontend_dist/assets/{AuditTrailView-5dnGX5El.js → AuditTrailView-FiUNTjpE.js} +1 -1
- modoboa/frontend_dist/assets/{CalendarView-DZONeDA9.js → CalendarView-DbWVDoWq.js} +1 -1
- modoboa/frontend_dist/assets/{ChoiceField-DnwXRkht.js → ChoiceField-17aCFpF6.js} +1 -1
- modoboa/frontend_dist/assets/{ComposeEmailForm-kghmfNuE.js → ComposeEmailForm-B-BP3lzX.js} +1 -1
- modoboa/frontend_dist/assets/ComposeEmailView-6JrFhK1W.js +1 -0
- modoboa/frontend_dist/assets/ConfirmDialog-Dr0i2kqK.js +1 -0
- modoboa/frontend_dist/assets/ConnectedLayout-irTI2ekJ.js +1 -0
- modoboa/frontend_dist/assets/{CreationForm-CW4lxnPg.js → CreationForm-BZMhQOez.js} +1 -1
- modoboa/frontend_dist/assets/DashboardView-CBpsEG3P.js +1 -0
- modoboa/frontend_dist/assets/{DomainAdminList-C3jcDDc3.js → DomainAdminList-BikGujRD.js} +1 -1
- modoboa/frontend_dist/assets/{DomainEditView-ph8AaElX.js → DomainEditView-SOvfeZWo.js} +1 -1
- modoboa/frontend_dist/assets/{DomainTransportForm-NCz6Bl-h.js → DomainTransportForm-CmFZ1Cxm.js} +1 -1
- modoboa/frontend_dist/assets/{DomainView-BgMSSuU-.js → DomainView-DasOm4jM.js} +3 -3
- modoboa/frontend_dist/assets/{DomainsView-CEEU9btK.js → DomainsView-DVJA4-oN.js} +1 -1
- modoboa/frontend_dist/assets/{EmailField-DeqDPm5j.js → EmailField-DpBYkqam.js} +1 -1
- modoboa/frontend_dist/assets/{EmailView-DczVhVO0.js → EmailView-D9vO7HHB.js} +1 -1
- modoboa/frontend_dist/assets/EmptyLayout-Dz3F8c7x.js +1 -0
- modoboa/frontend_dist/assets/{FiltersView-nJj_gSCx.js → FiltersView-FjdtF4HL.js} +1 -1
- modoboa/frontend_dist/assets/ForwardEmailView-DF5BQJjL.js +1 -0
- modoboa/frontend_dist/assets/{HtmlEditor-BWRdelVw.js → HtmlEditor-Zn0wjDog.js} +1 -1
- modoboa/frontend_dist/assets/{IdentitiesView-Dld9IloZ.js → IdentitiesView-Cf5eQoKA.js} +1 -1
- modoboa/frontend_dist/assets/{IdentitiesView-DPrrRMS5.css → IdentitiesView-D3AAiZLZ.css} +1 -1
- modoboa/frontend_dist/assets/{InformationView-BBWKSX8D.js → InformationView-gEYTQ37R.js} +1 -1
- modoboa/frontend_dist/assets/{LoadingData-G57nJ_JV.js → LoadingData-Cxd490P6.js} +1 -1
- modoboa/frontend_dist/assets/LoginCallbackView-C9zhejHH.js +1 -0
- modoboa/frontend_dist/assets/{LoginView-CqCCXYLo.js → LoginView-sWEH834U.js} +1 -1
- modoboa/frontend_dist/assets/MailboxView-Dn3iDym9.js +1 -0
- modoboa/frontend_dist/assets/{MenuItems-BqIZW5av.js → MenuItems-BRChEdLp.js} +1 -1
- modoboa/frontend_dist/assets/{MessageView-D_6tx_gd.js → MessageView-CDbJxbxm.js} +1 -1
- modoboa/frontend_dist/assets/{MessagesView-BH7JIR03.js → MessagesView-C_vBxVzN.js} +1 -1
- modoboa/frontend_dist/assets/{MigrationsView-Cv_So9T-.js → MigrationsView-D6peiQuR.js} +1 -1
- modoboa/frontend_dist/assets/{ParametersForm-3qXttTuQ.js → ParametersForm-Dip8Sobe.js} +1 -1
- modoboa/frontend_dist/assets/ParametersView-CwI0-9kV.js +1 -0
- modoboa/frontend_dist/assets/ParametersView-wFKfiEVR.js +1 -0
- modoboa/frontend_dist/assets/{ProviderEditView-zh7CY832.js → ProviderEditView-EH2NSz3x.js} +1 -1
- modoboa/frontend_dist/assets/{ProviderGeneralForm-BQU7t3ma.js → ProviderGeneralForm-CAVNOJp6.js} +1 -1
- modoboa/frontend_dist/assets/{ProvidersView-CoF_ZkZA.js → ProvidersView-BoH99d3a.js} +1 -1
- modoboa/frontend_dist/assets/QuarantineLayout--4g9urq2.js +1 -0
- modoboa/frontend_dist/assets/{QuarantineView-DNvpoycb.js → QuarantineView-BDzDrWwK.js} +1 -1
- modoboa/frontend_dist/assets/ReplyEmailView-D0VgYBnw.js +1 -0
- modoboa/frontend_dist/assets/ResourcesForm-C1YGkT8i.js +1 -0
- modoboa/frontend_dist/assets/SelfServiceLayout-BYkSLc4n.js +1 -0
- modoboa/frontend_dist/assets/{SettingsView-gQiJ2NVb.js → SettingsView-Cy5O-88Y.js} +1 -1
- modoboa/frontend_dist/assets/{StatisticsView-DYalet_q.js → StatisticsView-DtlBQK9z.js} +1 -1
- modoboa/frontend_dist/assets/{TimeSerieChart-BZ2htbFk.js → TimeSerieChart-DNjvmMDE.js} +1 -1
- modoboa/frontend_dist/assets/{TimeSerieChart-CxiwMzE8.css → TimeSerieChart-Dn6Wznn8.css} +1 -1
- modoboa/frontend_dist/assets/UserLayout-Cgqq7HVi.js +1 -0
- modoboa/frontend_dist/assets/{VAlert-4r6LxKtg.js → VAlert-x6n3drjw.js} +1 -1
- modoboa/frontend_dist/assets/{VApp-CX_C7AUN.js → VApp-C74A9rX5.js} +1 -1
- modoboa/frontend_dist/assets/{VAutocomplete-DNKmBvyZ.js → VAutocomplete-Bg2AlW_n.js} +1 -1
- modoboa/frontend_dist/assets/{VAvatar-DbuoZWmf.js → VAvatar-J563boKh.js} +1 -1
- modoboa/frontend_dist/assets/{VBadge-Bv2nvUmC.js → VBadge-CtEsXuih.js} +1 -1
- modoboa/frontend_dist/assets/{VCard-DzjUT5OP.js → VCard-CJ2oGh_i.js} +1 -1
- modoboa/frontend_dist/assets/{VCheckbox-dr7UFjl4.js → VCheckbox-VicuXIJq.js} +1 -1
- modoboa/frontend_dist/assets/{VCheckboxBtn-CpFdBnTv.js → VCheckboxBtn-CLh3m66z.js} +1 -1
- modoboa/frontend_dist/assets/{VChip-CaQvfmkw.js → VChip-DvxO74Mw.js} +1 -1
- modoboa/frontend_dist/assets/VColorPicker-DcVohVWE.js +1 -0
- modoboa/frontend_dist/assets/{VContainer-74Dnn8Ux.js → VContainer-L2EoH3dT.js} +1 -1
- modoboa/frontend_dist/assets/{VDataTable-CL7yHvG7.js → VDataTable-CAT0QYJz.js} +1 -1
- modoboa/frontend_dist/assets/{VDataTableServer-BqvNcIdw.js → VDataTableServer-DP13Fhfa.js} +1 -1
- modoboa/frontend_dist/assets/{VDataTableVirtual--KsOP8i6.js → VDataTableVirtual-LVNemzH7.js} +1 -1
- modoboa/frontend_dist/assets/{VDialog-DmTGCGR0.js → VDialog-CaQnQSDn.js} +1 -1
- modoboa/frontend_dist/assets/{VExpansionPanels-B7sSTCwd.js → VExpansionPanels-Dnwgt5uT.js} +1 -1
- modoboa/frontend_dist/assets/{VFileInput-SULIc6F3.js → VFileInput-z44hJIWr.js} +1 -1
- modoboa/frontend_dist/assets/{VForm-DsRLc-sa.js → VForm-BwTUBf4u.js} +1 -1
- modoboa/frontend_dist/assets/{VInput-DVKUObZe.js → VInput-Dpmea5z9.js} +1 -1
- modoboa/frontend_dist/assets/{VMenu-nv0XOgg0.js → VMenu-DDmBlbM8.js} +1 -1
- modoboa/frontend_dist/assets/{VPicker-DnDSWJHJ.js → VPicker-Dz7GbXwU.js} +1 -1
- modoboa/frontend_dist/assets/{VProgressCircular-qK6p5X_Y.js → VProgressCircular-CtqQON49.js} +1 -1
- modoboa/frontend_dist/assets/{VRadioGroup-CbiPLy0t.js → VRadioGroup-WW8uMAv4.js} +1 -1
- modoboa/frontend_dist/assets/{VRow-DJ0NB63-.js → VRow-CAbOSEHS.js} +1 -1
- modoboa/frontend_dist/assets/{VSelect-CxCFMHyF.js → VSelect-Cdh0qO5m.js} +1 -1
- modoboa/frontend_dist/assets/{VSelectionControl-C-6A4us5.js → VSelectionControl-BN-zQ3C6.js} +1 -1
- modoboa/frontend_dist/assets/{VSheet-DI6SxLnG.js → VSheet-9RDZpWCf.js} +1 -1
- modoboa/frontend_dist/assets/VSpacer-BWZq1PhD.js +1 -0
- modoboa/frontend_dist/assets/{VSwitch-DPnjPQuU.js → VSwitch-CixaedmQ.js} +1 -1
- modoboa/frontend_dist/assets/{VTable-ldTxgQPW.js → VTable-DO7ofPSj.js} +1 -1
- modoboa/frontend_dist/assets/{VTabs-aS8WSL9I.js → VTabs-C1-E1DJE.js} +1 -1
- modoboa/frontend_dist/assets/{VTextField-DKbr4H5w.js → VTextField-C9VUP7V9.js} +1 -1
- modoboa/frontend_dist/assets/{VTextarea-BttkFsM4.js → VTextarea-B28SmJ2v.js} +1 -1
- modoboa/frontend_dist/assets/{VToolbar-BxX3W2kR.js → VToolbar-Cs-fVa4M.js} +1 -1
- modoboa/frontend_dist/assets/VWindowItem-C9PFEbE6.js +1 -0
- modoboa/frontend_dist/assets/WebmailLayout-Dp6jIazW.js +1 -0
- modoboa/frontend_dist/assets/{accounts-CC2F0a0c.js → accounts-BT429O7P.js} +1 -1
- modoboa/frontend_dist/assets/{admin-CHCHFGI6.js → admin-Cu5rjY7m.js} +1 -1
- modoboa/frontend_dist/assets/{aliases-C9bUD4Ws.js → aliases-Q4uTcEh4.js} +1 -1
- modoboa/frontend_dist/assets/{amavis-BhzV4rgf.js → amavis-Be87kCqw.js} +1 -1
- modoboa/frontend_dist/assets/{amavis-DCVJxuui.js → amavis-Bx0pxvRn.js} +1 -1
- modoboa/frontend_dist/assets/{contacts-Dxz6eWpf.js → contacts-Dx240BT8.js} +1 -1
- modoboa/frontend_dist/assets/{domains-C2cornvL.js → domains-CQv8gtbQ.js} +1 -1
- modoboa/frontend_dist/assets/{domains.store-BLKRipG8.js → domains.store-CfpnnBGo.js} +1 -1
- modoboa/frontend_dist/assets/{filter-rmxrcjKk.js → filter-C_GJovn5.js} +1 -1
- modoboa/frontend_dist/assets/{forwardRefs-CpzzjgpX.js → forwardRefs-DPrLRqxP.js} +1 -1
- modoboa/frontend_dist/assets/{global.store-DndbMXYb.js → global.store-CNChIxQL.js} +1 -1
- modoboa/frontend_dist/assets/importExport-7sRHU_3R.js +1 -0
- modoboa/frontend_dist/assets/index-YIGRKgNA.js +982 -0
- modoboa/frontend_dist/assets/{layout-DbjDe3Wl.js → layout-BM4C5b2r.js} +1 -1
- modoboa/frontend_dist/assets/{layout.store-Vq5mvIp7.js → layout.store-CCOp6tv6.js} +1 -1
- modoboa/frontend_dist/assets/{logos-Bvcy0usu.js → logos-CboXciB_.js} +1 -1
- modoboa/frontend_dist/assets/{logs-BuItINky.js → logs-BeuijmJA.js} +1 -1
- modoboa/frontend_dist/assets/{parameters-C8IYEP7q.js → parameters-DpVw-Zs4.js} +1 -1
- modoboa/frontend_dist/assets/{parameters.store-1cwSP2JP.js → parameters.store-DRarkO6b.js} +1 -1
- modoboa/frontend_dist/assets/{permissions-DQjAcO9S.js → permissions-C33K6cJv.js} +1 -1
- modoboa/frontend_dist/assets/{ssrBoot-BxIQ9ccA.js → ssrBoot-CpJ-7WlH.js} +1 -1
- modoboa/frontend_dist/assets/{tag-3cfI1_f7.js → tag-DV1_zOzl.js} +1 -1
- modoboa/frontend_dist/assets/theme-BGzdVPko.js +1 -0
- modoboa/frontend_dist/assets/transports-B9mWU1W8.js +1 -0
- modoboa/frontend_dist/assets/{webmail-B2IUjaxM.js → webmail-DRyN8JIK.js} +1 -1
- modoboa/frontend_dist/index.html +1 -1
- modoboa/static/css/offline.css +7 -20
- modoboa/templates/registration/base.html +18 -0
- modoboa/templates/registration/login.html +1 -1
- modoboa/urls.py +1 -0
- {modoboa-2.5.1.dist-info → modoboa-2.6.0.dist-info}/METADATA +6 -6
- {modoboa-2.5.1.dist-info → modoboa-2.6.0.dist-info}/RECORD +150 -142
- {modoboa-2.5.1.dist-info → modoboa-2.6.0.dist-info}/licenses/LICENSE +1 -1
- modoboa/frontend_dist/assets/AccountLayout-U386K8zy.js +0 -1
- modoboa/frontend_dist/assets/AdminLayout-Cxm1lggg.js +0 -1
- modoboa/frontend_dist/assets/AlarmsView-Bcjsicac.js +0 -1
- modoboa/frontend_dist/assets/AliasEditView-Cx9410JP.css +0 -1
- modoboa/frontend_dist/assets/AliasEditView-k3rVt1tG.js +0 -1
- modoboa/frontend_dist/assets/AliasRecipientForm-IehUzKok.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailView-DLv3wk1k.js +0 -1
- modoboa/frontend_dist/assets/ConfirmDialog-CcPrCKuI.js +0 -1
- modoboa/frontend_dist/assets/ConnectedLayout-CWlBK7Hf.js +0 -1
- modoboa/frontend_dist/assets/DashboardView-DXVZMbMo.js +0 -1
- modoboa/frontend_dist/assets/EmptyLayout-BXgcfMLH.js +0 -1
- modoboa/frontend_dist/assets/ForwardEmailView-Bgv3JQb6.js +0 -1
- modoboa/frontend_dist/assets/LoginCallbackView-DjyE2SG_.js +0 -1
- modoboa/frontend_dist/assets/MailboxView-DRrs9eLO.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-3Ns04cpQ.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-B5B5Dt6K.js +0 -1
- modoboa/frontend_dist/assets/QuarantineLayout-CYBsrbJM.js +0 -1
- modoboa/frontend_dist/assets/ReplyEmailView-D1XTcglu.js +0 -1
- modoboa/frontend_dist/assets/ResourcesForm-BW8rUGgZ.js +0 -1
- modoboa/frontend_dist/assets/SelfServiceLayout-DfDHiYeX.js +0 -1
- modoboa/frontend_dist/assets/UserLayout-zUtHi-z-.js +0 -1
- modoboa/frontend_dist/assets/VColorPicker-ByGpCW5O.js +0 -1
- modoboa/frontend_dist/assets/VSpacer-CoJVmx8k.js +0 -1
- modoboa/frontend_dist/assets/VWindowItem-Cvqvdegd.js +0 -1
- modoboa/frontend_dist/assets/WebmailLayout-BT2k6U7q.js +0 -1
- modoboa/frontend_dist/assets/importExport-C3uqrcok.js +0 -1
- modoboa/frontend_dist/assets/index-LhNzkzAh.js +0 -984
- modoboa/frontend_dist/assets/transports-D4Jk4-AP.js +0 -1
- {modoboa-2.5.1.data → modoboa-2.6.0.data}/scripts/modoboa-admin.py +0 -0
- {modoboa-2.5.1.dist-info → modoboa-2.6.0.dist-info}/WHEEL +0 -0
- {modoboa-2.5.1.dist-info → modoboa-2.6.0.dist-info}/entry_points.txt +0 -0
- {modoboa-2.5.1.dist-info → modoboa-2.6.0.dist-info}/top_level.txt +0 -0
|
@@ -745,7 +745,7 @@ class CSVImportSerializer(serializers.Serializer):
|
|
|
745
745
|
class CSVIdentityImportSerializer(CSVImportSerializer):
|
|
746
746
|
"""Custom serializer for identity import."""
|
|
747
747
|
|
|
748
|
-
crypt_passwords = serializers.BooleanField()
|
|
748
|
+
crypt_passwords = serializers.BooleanField(default=False)
|
|
749
749
|
|
|
750
750
|
|
|
751
751
|
class AlarmSerializer(serializers.ModelSerializer):
|
modoboa/admin/models/domain.py
CHANGED
|
@@ -202,7 +202,7 @@ class Domain(mixins.MessageLimitMixin, AdminObject):
|
|
|
202
202
|
return self.dnsrecord_set.filter(type="autodiscover").first()
|
|
203
203
|
|
|
204
204
|
@cached_property
|
|
205
|
-
def allocated_quota(self):
|
|
205
|
+
def allocated_quota(self) -> int:
|
|
206
206
|
"""Return current quota allocation."""
|
|
207
207
|
if not self.quota:
|
|
208
208
|
return 0
|
modoboa/admin/models/mixins.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Admin model mixins."""
|
|
2
2
|
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
3
5
|
from modoboa.policyd.utils import get_message_counter
|
|
4
6
|
|
|
5
7
|
|
|
@@ -18,7 +20,7 @@ class MessageLimitMixin:
|
|
|
18
20
|
return self.message_limit - get_message_counter(self.message_counter_key)
|
|
19
21
|
|
|
20
22
|
@property
|
|
21
|
-
def sent_messages_in_percent(self):
|
|
23
|
+
def sent_messages_in_percent(self) -> Optional[int]: # noqa
|
|
22
24
|
"""Return number of sent messages as a percentage."""
|
|
23
25
|
if not self.message_limit:
|
|
24
26
|
return None
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<clientConfig version="1.1">
|
|
3
|
+
<emailProvider id="{{ domain }}">
|
|
4
|
+
<domain>{{ domain }}</domain>
|
|
5
|
+
<displayName>Modoboa</displayName>
|
|
6
|
+
<displayShortName>Mail</displayShortName>
|
|
7
|
+
|
|
8
|
+
<incomingServer type="imap">
|
|
9
|
+
<hostname>{{ connection_settings.imap.HOSTNAME }}</hostname>
|
|
10
|
+
<port>{{ connection_settings.imap.PORT }}</port>
|
|
11
|
+
<socketType>{{ connection_settings.imap.SOCKET_TYPE }}</socketType>
|
|
12
|
+
<authentication>plain</authentication>
|
|
13
|
+
<username>{{ emailaddress }}</username>
|
|
14
|
+
</incomingServer>
|
|
15
|
+
|
|
16
|
+
<outgoingServer type="smtp">
|
|
17
|
+
<hostname>{{ connection_settings.smtp.HOSTNAME }}</hostname>
|
|
18
|
+
<port>{{ connection_settings.smtp.PORT }}</port>
|
|
19
|
+
<socketType>{{ connection_settings.smtp.SOCKET_TYPE }}</socketType>
|
|
20
|
+
<authentication>plain</authentication>
|
|
21
|
+
<username>{{ emailaddress }}</username>
|
|
22
|
+
</outgoingServer>
|
|
23
|
+
</emailProvider>
|
|
24
|
+
</clientConfig>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
|
|
3
|
+
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
|
|
4
|
+
<Account>
|
|
5
|
+
<AccountType>email</AccountType>
|
|
6
|
+
<Action>settings</Action>
|
|
7
|
+
|
|
8
|
+
<Protocol>
|
|
9
|
+
<Type>IMAP</Type>
|
|
10
|
+
<Server>{{ connection_settings.imap.HOSTNAME }}</Server>
|
|
11
|
+
<Port>{{ connection_settings.imap.PORT }}</Port>
|
|
12
|
+
<SSL>true</SSL>
|
|
13
|
+
<LoginName>{{ emailaddress }}</LoginName>
|
|
14
|
+
</Protocol>
|
|
15
|
+
|
|
16
|
+
<Protocol>
|
|
17
|
+
<Type>SMTP</Type>
|
|
18
|
+
<Server>{{ connection_settings.smtp.HOSTNAME }}</Server>
|
|
19
|
+
<Port>{{ connection_settings.smtp.PORT }}</Port>
|
|
20
|
+
<SSL>false</SSL>
|
|
21
|
+
<TLS>true</TLS>
|
|
22
|
+
<LoginName>{{ emailaddress }}</LoginName>
|
|
23
|
+
</Protocol>
|
|
24
|
+
|
|
25
|
+
</Account>
|
|
26
|
+
</Response>
|
|
27
|
+
</Autodiscover>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
from django.urls import reverse
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ViewsTestCase(TestCase):
|
|
6
|
+
|
|
7
|
+
databases = "__all__"
|
|
8
|
+
|
|
9
|
+
def test_autoconfig(self):
|
|
10
|
+
url = reverse("autoconfig:autoconfig")
|
|
11
|
+
|
|
12
|
+
resp = self.client.get(url)
|
|
13
|
+
self.assertEqual(resp.status_code, 404)
|
|
14
|
+
|
|
15
|
+
resp = self.client.get(f"{url}?emailaddress=test@test.com")
|
|
16
|
+
self.assertEqual(resp.status_code, 200)
|
|
17
|
+
|
|
18
|
+
self.assertIn(b'<emailProvider id="test.com">', resp.content)
|
|
19
|
+
|
|
20
|
+
def test_autodiscover(self):
|
|
21
|
+
url = reverse("autoconfig:autodiscover")
|
|
22
|
+
|
|
23
|
+
resp = self.client.post(url)
|
|
24
|
+
self.assertEqual(resp.status_code, 404)
|
|
25
|
+
|
|
26
|
+
resp = self.client.post(url, {"EmailAddress": "test@test.com"})
|
|
27
|
+
self.assertEqual(resp.status_code, 200)
|
|
28
|
+
|
|
29
|
+
self.assertIn(b"<LoginName>test@test.com", resp.content)
|
|
30
|
+
|
|
31
|
+
def test_mobileconfig(self):
|
|
32
|
+
url = reverse("autoconfig:mobileconfig")
|
|
33
|
+
|
|
34
|
+
resp = self.client.get(url)
|
|
35
|
+
self.assertEqual(resp.status_code, 404)
|
|
36
|
+
|
|
37
|
+
resp = self.client.get(f"{url}?emailaddress=test@test.com")
|
|
38
|
+
self.assertEqual(resp.status_code, 200)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Autoconfig urls."""
|
|
2
|
+
|
|
3
|
+
from django.urls import path
|
|
4
|
+
|
|
5
|
+
from modoboa.autoconfig import views
|
|
6
|
+
|
|
7
|
+
app_name = "autoconfig"
|
|
8
|
+
|
|
9
|
+
urlpatterns = [
|
|
10
|
+
path(
|
|
11
|
+
"mail/config-v1.1.xml",
|
|
12
|
+
views.AutoConfigView.as_view(),
|
|
13
|
+
name="autoconfig",
|
|
14
|
+
),
|
|
15
|
+
path(
|
|
16
|
+
"autodiscover/autodiscover.xml",
|
|
17
|
+
views.AutoDiscoverView.as_view(),
|
|
18
|
+
name="autodiscover",
|
|
19
|
+
),
|
|
20
|
+
path("mobileconfig", views.MobileConfigView.as_view(), name="mobileconfig"),
|
|
21
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import plistlib
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.http import Http404, HttpResponse
|
|
6
|
+
from django.utils.decorators import method_decorator
|
|
7
|
+
from django.views import generic
|
|
8
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
9
|
+
|
|
10
|
+
from modoboa.lib.email_utils import split_address
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ConfigBaseMixin:
|
|
14
|
+
|
|
15
|
+
content_type = "application/xml"
|
|
16
|
+
|
|
17
|
+
def get_common_context(self, emailaddress: str) -> dict:
|
|
18
|
+
local_part, domain = split_address(emailaddress)
|
|
19
|
+
return {
|
|
20
|
+
"emailaddress": emailaddress,
|
|
21
|
+
"domain": domain,
|
|
22
|
+
"connection_settings": settings.EMAIL_CLIENT_CONNECTION_SETTINGS,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AutoConfigView(ConfigBaseMixin, generic.TemplateView):
|
|
27
|
+
|
|
28
|
+
http_method_names = ["get"]
|
|
29
|
+
template_name = "autoconfig/autoconfig.xml"
|
|
30
|
+
|
|
31
|
+
def get_context_data(self, **kwargs):
|
|
32
|
+
emailaddress = self.request.GET.get("emailaddress")
|
|
33
|
+
if not emailaddress:
|
|
34
|
+
raise Http404
|
|
35
|
+
context = super().get_context_data(**kwargs)
|
|
36
|
+
context.update(self.get_common_context(emailaddress))
|
|
37
|
+
return context
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@method_decorator(csrf_exempt, name="dispatch")
|
|
41
|
+
class AutoDiscoverView(ConfigBaseMixin, generic.TemplateView):
|
|
42
|
+
|
|
43
|
+
http_method_names = ["post"]
|
|
44
|
+
template_name = "autoconfig/autodiscover.xml"
|
|
45
|
+
|
|
46
|
+
def get_context_data(self, **kwargs):
|
|
47
|
+
emailaddress = self.request.POST.get("EmailAddress")
|
|
48
|
+
if not emailaddress:
|
|
49
|
+
raise Http404
|
|
50
|
+
context = super().get_context_data(**kwargs)
|
|
51
|
+
context.update(self.get_common_context(emailaddress))
|
|
52
|
+
return context
|
|
53
|
+
|
|
54
|
+
def post(self, request, *args, **kwargs):
|
|
55
|
+
context = self.get_context_data(**kwargs)
|
|
56
|
+
return self.render_to_response(context)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MobileConfigView(generic.View):
|
|
60
|
+
|
|
61
|
+
http_method_names = ["get"]
|
|
62
|
+
|
|
63
|
+
def get(self, request, *args, **kwargs):
|
|
64
|
+
emailaddress = self.request.GET.get("emailaddress")
|
|
65
|
+
if not emailaddress:
|
|
66
|
+
raise Http404
|
|
67
|
+
local_part, domain = split_address(emailaddress)
|
|
68
|
+
parts = domain.split(".")
|
|
69
|
+
parts.reverse()
|
|
70
|
+
reverse_domain = ".".join(parts)
|
|
71
|
+
imap_settings = settings.EMAIL_CLIENT_CONNECTION_SETTINGS["imap"]
|
|
72
|
+
smtp_settings = settings.EMAIL_CLIENT_CONNECTION_SETTINGS["smtp"]
|
|
73
|
+
profile = {
|
|
74
|
+
"PayloadType": "Configuration",
|
|
75
|
+
"PayloadVersion": 1,
|
|
76
|
+
"PayloadIdentifier": f"{reverse_domain}.mailprofile",
|
|
77
|
+
"PayloadUUID": str(uuid.uuid4()),
|
|
78
|
+
"PayloadDisplayName": f"{domain} Mail Configuration",
|
|
79
|
+
"PayloadOrganization": domain,
|
|
80
|
+
"PayloadContent": [
|
|
81
|
+
{
|
|
82
|
+
"PayloadType": "com.apple.mail.managed",
|
|
83
|
+
"PayloadVersion": 1,
|
|
84
|
+
"PayloadIdentifier": f"{reverse_domain}.mailprofile.mail",
|
|
85
|
+
"PayloadUUID": str(uuid.uuid4()),
|
|
86
|
+
"PayloadDisplayName": "Mail",
|
|
87
|
+
"EmailAccountDescription": f"{domain} Mail",
|
|
88
|
+
"EmailAccountType": "EmailTypeIMAP",
|
|
89
|
+
"EmailAddress": emailaddress,
|
|
90
|
+
# incoming
|
|
91
|
+
"IncomingMailServerHostName": imap_settings["HOSTNAME"],
|
|
92
|
+
"IncomingMailServerPortNumber": imap_settings["PORT"],
|
|
93
|
+
"IncomingMailServerUseSSL": True,
|
|
94
|
+
"IncomingMailServerUsername": emailaddress,
|
|
95
|
+
# outgoing
|
|
96
|
+
"OutgoingMailServerHostName": smtp_settings["HOSTNAME"],
|
|
97
|
+
"OutgoingMailServerPortNumber": smtp_settings["PORT"],
|
|
98
|
+
"OutgoingMailServerUseSSL": True,
|
|
99
|
+
"OutgoingMailServerUsername": emailaddress,
|
|
100
|
+
"OutgoingPasswordSameAsIncomingPassword": True,
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
}
|
|
104
|
+
plist_bytes = plistlib.dumps(profile, fmt=plistlib.FMT_XML)
|
|
105
|
+
resp = HttpResponse(
|
|
106
|
+
plist_bytes, content_type="application/x-apple-aspen-config"
|
|
107
|
+
)
|
|
108
|
+
resp["Content-Disposition"] = 'attachment; filename="mail.mobileconfig"'
|
|
109
|
+
return resp
|
|
@@ -62,7 +62,7 @@ class ARMessageViewSetTestCase(PatcherMixin, ModoAPITestCase):
|
|
|
62
62
|
def test_retrieve_armessage_domadmin(self):
|
|
63
63
|
admin = User.objects.get(username="admin@test.com")
|
|
64
64
|
self.client.logout()
|
|
65
|
-
self.client.
|
|
65
|
+
self.client.force_authenticate(admin)
|
|
66
66
|
url = reverse("api:armessage-list")
|
|
67
67
|
response = self.client.get(url)
|
|
68
68
|
self.assertEqual(response.status_code, 200)
|
|
@@ -74,7 +74,7 @@ class ARMessageViewSetTestCase(PatcherMixin, ModoAPITestCase):
|
|
|
74
74
|
|
|
75
75
|
def test_retrieve_armessage_simpleuser(self):
|
|
76
76
|
self.client.logout()
|
|
77
|
-
self.client.
|
|
77
|
+
self.client.force_authenticate(self.account)
|
|
78
78
|
url = reverse("api:armessage-list")
|
|
79
79
|
response = self.client.get(url)
|
|
80
80
|
self.assertEqual(response.status_code, 200)
|
|
@@ -156,7 +156,7 @@ class AccountARMessageViewSetTestCase(PatcherMixin, ModoAPITestCase):
|
|
|
156
156
|
response = self.client.get(url)
|
|
157
157
|
self.assertEqual(response.status_code, 403)
|
|
158
158
|
self.client.logout()
|
|
159
|
-
self.client.
|
|
159
|
+
self.client.force_authenticate(self.account)
|
|
160
160
|
self.assertFalse(
|
|
161
161
|
models.ARmessage.objects.filter(mbox=self.account.mailbox).exists()
|
|
162
162
|
)
|
|
@@ -168,7 +168,7 @@ class AccountARMessageViewSetTestCase(PatcherMixin, ModoAPITestCase):
|
|
|
168
168
|
|
|
169
169
|
def test_set_armessage(self):
|
|
170
170
|
self.client.logout()
|
|
171
|
-
self.client.
|
|
171
|
+
self.client.force_authenticate(self.account)
|
|
172
172
|
url = reverse("api:account_armessage-armessage")
|
|
173
173
|
fromdate = timezone.now()
|
|
174
174
|
todate = fromdate + relativedelta(days=4)
|
modoboa/calendars/tests.py
CHANGED
|
@@ -13,7 +13,7 @@ from modoboa.admin import factories as admin_factories
|
|
|
13
13
|
from modoboa.admin import models as admin_models
|
|
14
14
|
from modoboa.core import factories as core_factories
|
|
15
15
|
from modoboa.core import models as core_models
|
|
16
|
-
from modoboa.lib.tests import
|
|
16
|
+
from modoboa.lib.tests import ModoAPITestCase
|
|
17
17
|
|
|
18
18
|
from modoboa.admin.factories import populate_database
|
|
19
19
|
|
|
@@ -53,7 +53,7 @@ class TestDataMixin:
|
|
|
53
53
|
cls.scalendar2 = factories.SharedCalendarFactory(domain=cls.domain2)
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
class AccessRuleTestCase(
|
|
56
|
+
class AccessRuleTestCase(ModoAPITestCase):
|
|
57
57
|
|
|
58
58
|
@classmethod
|
|
59
59
|
def setUpTestData(cls):
|
|
@@ -139,7 +139,7 @@ class UserCalendarViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
139
139
|
|
|
140
140
|
def setUp(self):
|
|
141
141
|
"""Initiate test context."""
|
|
142
|
-
self.client.
|
|
142
|
+
self.client.force_authenticate(self.account)
|
|
143
143
|
self.set_global_parameter("server_location", "http://localhost:5232")
|
|
144
144
|
|
|
145
145
|
def test_get_calendars(self):
|
|
@@ -214,7 +214,7 @@ class SharedCalendarViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
214
214
|
|
|
215
215
|
def setUp(self):
|
|
216
216
|
"""Initiate test context."""
|
|
217
|
-
self.client.
|
|
217
|
+
self.client.force_authenticate(self.admin_account)
|
|
218
218
|
self.set_global_parameter("server_location", "http://localhost:5232")
|
|
219
219
|
|
|
220
220
|
def test_get_calendars(self):
|
|
@@ -304,7 +304,7 @@ class AccessRuleViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
304
304
|
|
|
305
305
|
def setUp(self):
|
|
306
306
|
"""Initiate test context."""
|
|
307
|
-
self.client.
|
|
307
|
+
self.client.force_authenticate(self.account)
|
|
308
308
|
|
|
309
309
|
def test_get_accessrules(self):
|
|
310
310
|
"""Test access rule retrieval."""
|
|
@@ -383,7 +383,7 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
383
383
|
self.cal_mock.return_value = mocks.Calendar(client=self.client_mock)
|
|
384
384
|
self.addCleanup(patcher2.stop)
|
|
385
385
|
|
|
386
|
-
self.client.
|
|
386
|
+
self.client.force_authenticate(self.account)
|
|
387
387
|
self.set_global_parameter("server_location", "http://localhost")
|
|
388
388
|
|
|
389
389
|
def test_get_user_events(self):
|
|
@@ -550,7 +550,7 @@ class AttendeeViewSetTestCase(ModoAPITestCase):
|
|
|
550
550
|
|
|
551
551
|
def setUp(self):
|
|
552
552
|
"""Initiate test context."""
|
|
553
|
-
self.client.
|
|
553
|
+
self.client.force_authenticate(self.account)
|
|
554
554
|
|
|
555
555
|
def test_get_attendees(self):
|
|
556
556
|
"""Test attendees retrieval."""
|
|
@@ -571,7 +571,7 @@ class MailboxViewSetTestCase(ModoAPITestCase):
|
|
|
571
571
|
|
|
572
572
|
def setUp(self):
|
|
573
573
|
"""Initiate test context."""
|
|
574
|
-
self.client.
|
|
574
|
+
self.client.force_authenticate(self.account)
|
|
575
575
|
|
|
576
576
|
def test_get_mailboxes(self):
|
|
577
577
|
"""Test mailbox retrieval."""
|
modoboa/contacts/tests.py
CHANGED
|
@@ -11,7 +11,7 @@ from django.utils import timezone
|
|
|
11
11
|
|
|
12
12
|
from modoboa.admin import factories as admin_factories
|
|
13
13
|
from modoboa.core import models as core_models
|
|
14
|
-
from modoboa.lib.tests import ModoAPITestCase
|
|
14
|
+
from modoboa.lib.tests import ModoAPITestCase
|
|
15
15
|
|
|
16
16
|
from . import factories
|
|
17
17
|
from . import mocks
|
|
@@ -46,7 +46,7 @@ class TestDataMixin:
|
|
|
46
46
|
|
|
47
47
|
def setUp(self, *args, **kwargs):
|
|
48
48
|
"""Initiate test context."""
|
|
49
|
-
self.client.
|
|
49
|
+
self.client.force_authenticate(self.user)
|
|
50
50
|
self.set_global_parameter(
|
|
51
51
|
"server_location", "http://example.test/radicale/", app="calendars"
|
|
52
52
|
)
|
|
@@ -72,8 +72,7 @@ class ViewsTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
72
72
|
def test_user_settings(self):
|
|
73
73
|
"""Check that remote collection creation request is sent."""
|
|
74
74
|
# 1. Addressbook with contacts must be synced manually
|
|
75
|
-
|
|
76
|
-
self.client.post(reverse("core:login"), data)
|
|
75
|
+
self.client.force_authenticate(self.user)
|
|
77
76
|
self.enable_cdav_sync()
|
|
78
77
|
self.addressbook.refresh_from_db()
|
|
79
78
|
self.assertIs(self.addressbook.last_sync, None)
|
|
@@ -81,8 +80,7 @@ class ViewsTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
81
80
|
# 2. Addressbook with no contacts can be considered synced
|
|
82
81
|
user = core_models.User.objects.get(username="user@test2.com")
|
|
83
82
|
abook = user.addressbook_set.first()
|
|
84
|
-
|
|
85
|
-
self.client.post(reverse("core:login"), data)
|
|
83
|
+
self.client.force_authenticate(user)
|
|
86
84
|
abook.refresh_from_db()
|
|
87
85
|
self.assertIs(abook.last_sync, None)
|
|
88
86
|
# Now enable sync.
|
|
@@ -312,7 +310,7 @@ class EmailAddressViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
312
310
|
self.assertEqual(len(response.data), 3)
|
|
313
311
|
|
|
314
312
|
|
|
315
|
-
class ImportTestCase(TestDataMixin,
|
|
313
|
+
class ImportTestCase(TestDataMixin, ModoAPITestCase):
|
|
316
314
|
|
|
317
315
|
def setUp(self):
|
|
318
316
|
super().setUp()
|
modoboa/core/api/v1/tests.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from django.urls import reverse_lazy
|
|
2
2
|
|
|
3
|
-
from modoboa.lib.tests import
|
|
3
|
+
from modoboa.lib.tests import ModoAPITestCase
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class OpenAPITestCase(
|
|
6
|
+
class OpenAPITestCase(ModoAPITestCase):
|
|
7
7
|
openapi_schema_url = reverse_lazy("schema-v1-legacy")
|
|
8
8
|
|
|
9
9
|
def test_unauthorized(self):
|
|
@@ -38,6 +38,19 @@ SITE_ID = 1
|
|
|
38
38
|
# The email address that error messages come from, such as those sent to ADMINS
|
|
39
39
|
#SERVER_EMAIL = 'webmaster@example.net'
|
|
40
40
|
|
|
41
|
+
EMAIL_CLIENT_CONNECTION_SETTINGS = {
|
|
42
|
+
'imap': {
|
|
43
|
+
'HOSTNAME': '{{ allowed_host }}',
|
|
44
|
+
'SOCKET_TYPE': 'SSL',
|
|
45
|
+
'PORT': 993,
|
|
46
|
+
},
|
|
47
|
+
'smtp': {
|
|
48
|
+
'HOSTNAME': '{{ allowed_host }}',
|
|
49
|
+
'SOCKET_TYPE': 'STARTTLS',
|
|
50
|
+
'PORT': 587
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
# Security settings
|
|
42
55
|
|
|
43
56
|
X_FRAME_OPTIONS = "SAMEORIGIN"
|
|
@@ -75,6 +88,7 @@ MODOBOA_APPS = (
|
|
|
75
88
|
'modoboa.core',
|
|
76
89
|
'modoboa.lib',
|
|
77
90
|
'modoboa.admin',
|
|
91
|
+
'modoboa.autoconfig',
|
|
78
92
|
'modoboa.transport',
|
|
79
93
|
'modoboa.relaydomains',
|
|
80
94
|
'modoboa.limits',
|
|
@@ -221,7 +235,6 @@ REST_FRAMEWORK = {
|
|
|
221
235
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
|
222
236
|
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
|
223
237
|
'rest_framework.authentication.TokenAuthentication',
|
|
224
|
-
'rest_framework.authentication.SessionAuthentication',
|
|
225
238
|
),
|
|
226
239
|
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
|
227
240
|
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
|
modoboa/core/models.py
CHANGED
modoboa/core/tests/test_ldap.py
CHANGED
|
@@ -158,6 +158,8 @@ class ProfileTestCase(LDAPTestCaseMixin, ModoAPITestCase):
|
|
|
158
158
|
|
|
159
159
|
username = "testuser@example.com"
|
|
160
160
|
self.authenticate(username, "test")
|
|
161
|
+
user = models.User.objects.get(username=username)
|
|
162
|
+
self.client.force_authenticate(user)
|
|
161
163
|
response = self.client.post(
|
|
162
164
|
reverse("v2:account-set-me-password"),
|
|
163
165
|
{"password": "test", "new_password": "Toto1234"},
|
modoboa/core/views/auth.py
CHANGED
|
@@ -37,6 +37,23 @@ from .. import signals
|
|
|
37
37
|
logger = logging.getLogger("modoboa.auth")
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
class ModoboaThemeMixin:
|
|
41
|
+
|
|
42
|
+
def dispatch(self, request, *args, **kwargs):
|
|
43
|
+
self.parameters = dict(param_tools.get_global_parameters(app="core"))
|
|
44
|
+
return super().dispatch(request, *args, **kwargs)
|
|
45
|
+
|
|
46
|
+
def get_context_data(self, **kwargs):
|
|
47
|
+
context = super().get_context_data(**kwargs)
|
|
48
|
+
context.update(
|
|
49
|
+
{
|
|
50
|
+
"theme_primary_color": self.parameters["theme_primary_color"],
|
|
51
|
+
"theme_primary_color_dark": self.parameters["theme_primary_color_dark"],
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
return context
|
|
55
|
+
|
|
56
|
+
|
|
40
57
|
class LoginViewMixin:
|
|
41
58
|
@cached_property
|
|
42
59
|
def logger(self):
|
|
@@ -74,7 +91,7 @@ class LoginViewMixin:
|
|
|
74
91
|
return response
|
|
75
92
|
|
|
76
93
|
|
|
77
|
-
class LoginView(LoginViewMixin, auth_views.LoginView):
|
|
94
|
+
class LoginView(ModoboaThemeMixin, LoginViewMixin, auth_views.LoginView):
|
|
78
95
|
"""Login view with 2FA support."""
|
|
79
96
|
|
|
80
97
|
form_class = forms.AuthenticationForm
|
|
@@ -86,13 +103,15 @@ class LoginView(LoginViewMixin, auth_views.LoginView):
|
|
|
86
103
|
sender="login", location="loginpage"
|
|
87
104
|
)
|
|
88
105
|
announcements = [announcement[1] for announcement in announcements]
|
|
89
|
-
context.update(
|
|
106
|
+
context.update(
|
|
107
|
+
{
|
|
108
|
+
"announcements": announcements,
|
|
109
|
+
}
|
|
110
|
+
)
|
|
90
111
|
return context
|
|
91
112
|
|
|
92
113
|
def check_password_hash(self, user, form):
|
|
93
|
-
condition = user.is_local and
|
|
94
|
-
"update_scheme", raise_exception=False
|
|
95
|
-
)
|
|
114
|
+
condition = user.is_local and self.parameters.get("update_scheme")
|
|
96
115
|
if not condition:
|
|
97
116
|
return
|
|
98
117
|
# check if password scheme is correct
|
|
@@ -157,7 +176,7 @@ def dologout(request):
|
|
|
157
176
|
return HttpResponseRedirect(reverse("core:login"))
|
|
158
177
|
|
|
159
178
|
|
|
160
|
-
class PasswordResetView(auth_views.PasswordResetView):
|
|
179
|
+
class PasswordResetView(ModoboaThemeMixin, auth_views.PasswordResetView):
|
|
161
180
|
"""Custom view to override form."""
|
|
162
181
|
|
|
163
182
|
form_class = forms.PasswordResetForm
|
|
@@ -202,7 +221,7 @@ class PasswordResetView(auth_views.PasswordResetView):
|
|
|
202
221
|
return HttpResponseRedirect(reverse("password_reset_confirm_code"))
|
|
203
222
|
|
|
204
223
|
|
|
205
|
-
class VerifySMSCodeView(generic.FormView):
|
|
224
|
+
class VerifySMSCodeView(ModoboaThemeMixin, generic.FormView):
|
|
206
225
|
"""View to verify a code received by SMS."""
|
|
207
226
|
|
|
208
227
|
form_class = forms.VerifySMSCodeForm
|
|
@@ -254,7 +273,7 @@ class ResendSMSCodeView(generic.View):
|
|
|
254
273
|
return JsonResponse({"status": "ok"})
|
|
255
274
|
|
|
256
275
|
|
|
257
|
-
class TwoFactorCodeVerifyView(LoginViewMixin, generic.FormView):
|
|
276
|
+
class TwoFactorCodeVerifyView(ModoboaThemeMixin, LoginViewMixin, generic.FormView):
|
|
258
277
|
"""View to verify a 2FA code after login."""
|
|
259
278
|
|
|
260
279
|
form_class = forms.Verify2FACodeForm
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{C as q}from"./ChoiceField-
|
|
1
|
+
import{C as q}from"./ChoiceField-17aCFpF6.js";import{u as M,b as S,h as V,i as f,j as _,c as R,o as y,w as h,a as d,f as s,e as U,t as x,k as N,_ as C,z as D,m as j,l as Q,F as T,s as G}from"./index-YIGRKgNA.js";import{V as H,a as L}from"./VRow-CAbOSEHS.js";import{V as B}from"./VAlert-x6n3drjw.js";import{c as P}from"./VAvatar-J563boKh.js";import{V as k,r as F}from"./VForm-BwTUBf4u.js";import{_ as z}from"./AccountPasswordSubForm-B9X5wAXp.js";import{E as I}from"./EmailField-DpBYkqam.js";import{V as E}from"./VTextField-C9VUP7V9.js";import{V as $}from"./VSwitch-CixaedmQ.js";import{u as K}from"./domains.store-CfpnnBGo.js";import{a as Y}from"./accounts-BT429O7P.js";import{V as J}from"./VChip-DvxO74Mw.js";const W={class:"headline"},X={class:"mt-4"},Ve={__name:"AccountRoleForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const i=M(),{$gettext:l}=S(),v=g,m=V(),c=f(()=>v.modelValue),a=f(()=>i.authUser),p=f(()=>{const r=b.value.find(n=>n.value===c.value.role);return r!==void 0?r.label:""}),e=f(()=>{const r=b.value.find(n=>n.value===c.value.role);return r!==void 0?r.help:""}),b=f(()=>a.value.role===_.SUPER_ADMIN?[...A,...o,...u,...t]:a.value.role===_.DOMAIN_ADMIN?[...A]:a.value.role===_.RESELLER?[...o,...A]:[]),A=[{label:l("Simple user"),value:_.USER,help:l("A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.")}],o=[{label:l("Domain administrator"),value:_.DOMAIN_ADMIN,help:l("A user with privileges on one or more domain. He will be allowed to administer mailboxes and he can also have a mailbox.")}],u=[{label:l("Reseller"),value:_.RESELLER,help:l("An intermediate user who has the same privileges than a Domain administrator, plus the possibility to create domains and to assign resources.")}],t=[{label:l("Super administrator"),value:_.SUPER_ADMIN,help:l("A user with all privileges, can do anything. By default, such a user does not have a mailbox so he can't access end-user features.")}];return w({vFormRef:m,roleLabel:p}),(r,n)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[d(H,null,{default:h(()=>[d(L,{cols:"7"},{default:h(()=>[d(s(q),{modelValue:c.value.role,"onUpdate:modelValue":n[0]||(n[0]=O=>c.value.role=O),label:s(l)("Choose a role"),choices:b.value,"choices-per-line":2},null,8,["modelValue","label","choices"])]),_:1}),d(L,{cols:"5"},{default:h(()=>[d(B,{color:"primary",class:"mt-11 ml-4 rounded-lg"},{default:h(()=>[U("h3",W,x(p.value),1),U("p",X,x(e.value),1),d(P,{color:"white",class:"float-right",size:"large",icon:"mdi-help-circle-outline"})]),_:1})]),_:1})]),_:1})]),_:1},512))}},Z={class:"m-label"},ee={class:"m-label"},ae={class:"m-label"},he={__name:"AccountGeneralForm",props:{modelValue:{type:Object,default:null},editing:{type:Boolean,default:!1}},setup(g,{expose:w}){const{$gettext:i}=S(),l=M(),v=g,m=V(),c=V({}),a=f(()=>v.modelValue),p=f(()=>a.value.role===_.USER?i("Enter an email address"):i("Enter a simple username or an email address")),e=f(()=>a.value.role===_.USER?"email":"text");function b(){a.value.username.indexOf("@")!==-1&&(a.value.mailbox.full_address=a.value.username,a.value.mailbox.message_limit=null)}return w({vFormRef:m,formErrors:c}),(A,o)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[U("label",Z,x(s(i)("Username")),1),d(I,{ref:"username",modelValue:a.value.username,"onUpdate:modelValue":[o[0]||(o[0]=u=>a.value.username=u),b],placeholder:p.value,type:e.value,rules:e.value==="email"?[s(F).required,s(F).email]:[s(F).required],role:a.value.role,"error-msg":c.value.value?c.value.value.username:[]},null,8,["modelValue","placeholder","type","rules","role","error-msg"]),U("label",ee,x(s(i)("First name")),1),d(E,{modelValue:a.value.first_name,"onUpdate:modelValue":o[1]||(o[1]=u=>a.value.first_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),U("label",ae,x(s(i)("Last name")),1),d(E,{modelValue:a.value.last_name,"onUpdate:modelValue":o[2]||(o[2]=u=>a.value.last_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),s(l).authUser.pk!==a.value.pk?(y(),R(z,{key:0,ref:"passwordForm",modelValue:a.value,"onUpdate:modelValue":o[3]||(o[3]=u=>a.value=u),editing:g.editing,"form-errors":c.value},null,8,["modelValue","editing","form-errors"])):(y(),R(B,{key:1,type:"info",variant:"tonal",class:"py-2"},{default:h(()=>[N(x(s(i)("You can update your password from the Account section")),1)]),_:1})),d($,{modelValue:a.value.is_active,"onUpdate:modelValue":o[4]||(o[4]=u=>a.value.is_active=u),label:s(i)("Enabled"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},le={class:"m-label"},oe={class:"m-label"},te={__name:"AccountMailboxForm",props:{modelValue:{type:Object,default:null}},emits:["update:modelValue"],setup(g,{expose:w,emit:i}){const{$gettext:l}=S(),v=i,m=g,c=K(),a=f(()=>c.domains),p=V(),e=V({mailbox:{}});D(m.modelValue,t=>{t?(e.value={...t},e.value.role===_.USER&&(e.value.mailbox||(e.value.mailbox={}),e.value.mailbox.full_address=e.value.username),e.value.mailbox.message_limit===""&&(e.value.mailbox.message_limit=null)):e.value={mailbox:{}}},{immediate:!0}),D(e,t=>{v("update:modelValue",t)},{deep:!0});const b=f(()=>{const t=e.value.mailbox.full_address;if(t&&t.indexOf("@")!==-1){const r=a.value.find(n=>n.name===t.split("@")[1]);if(r)return parseInt(r.default_mailbox_quota)}}),A=f(()=>{let t=l("Use domain default value");return b.value!==void 0&&(b.value===0?t+=` (${l("unlimited")})`:t+=` (${b.value} MB)`),t});function o(t){t&&delete e.value.mailbox.quota}function u(t){t===""&&(e.value.mailbox.message_limit=null)}return w({vFormRef:p}),(t,r)=>(y(),R(k,{ref_key:"vFormRef",ref:p},{default:h(()=>[U("label",le,x(s(l)("Quota")),1),d($,{modelValue:e.value.mailbox.use_domain_quota,"onUpdate:modelValue":[r[0]||(r[0]=n=>e.value.mailbox.use_domain_quota=n),o],label:A.value,color:"primary"},null,8,["modelValue","label"]),e.value.mailbox.use_domain_quota?j("",!0):(y(),R(E,{key:0,modelValue:e.value.mailbox.quota,"onUpdate:modelValue":r[1]||(r[1]=n=>e.value.mailbox.quota=n),placeholder:s(l)("Ex: 10MB. Leave empty for no limit"),hint:s(l)("Quota for this mailbox, can be expressed in KB, MB (default) or GB. Define a custom value or use domain's default one. Leave empty to define an unlimited value (not allowed for domain administrators)."),"persistent-hint":"",variant:"outlined",density:"compact"},null,8,["modelValue","placeholder","hint"])),U("label",oe,x(s(l)("Daily message sending limit")),1),d(E,{modelValue:e.value.mailbox.message_limit,"onUpdate:modelValue":[r[2]||(r[2]=n=>e.value.mailbox.message_limit=n),u],placeholder:s(l)("Leave empty for no limit"),hint:s(l)("Number of messages this mailbox can send per day. Leave empty for no limit."),"persistent-hint":"",variant:"outlined",density:"compact",rules:[s(F).numericOrNull]},null,8,["modelValue","placeholder","hint","rules"]),d($,{modelValue:e.value.mailbox.is_send_only,"onUpdate:modelValue":r[3]||(r[3]=n=>e.value.mailbox.is_send_only=n),label:s(l)("Send only account"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},xe=C(te,[["__scopeId","data-v-41714779"]]),ye={__name:"AccountAliasForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const{$gettext:i}=S(),l=g,v=f(()=>l.modelValue),m=V(""),c=V(),a=V(),p=V(!1),e=V([]);async function b(){if(e.value=[],p.value=!0,v.value.aliases.indexOf(m.value)!==-1){e.value=[i("Alias already added")],m.value="",p.value=!1;return}try{await Y.validate({aliases:[m.value],mailbox:v.value.mailbox}),v.value.aliases.push(m.value),m.value="",a.value.resetValidation()}catch(o){o.response.data.aliases?o.response.data.aliases:o.response.data.non_field_errors&&o.response.data.non_field_errors}finally{p.value=!1}}function A(o){v.value.aliases.splice(o,1)}return w({vFormRef:a}),(o,u)=>(y(),R(k,{ref_key:"vFormRef",ref:a},{default:h(()=>[d(s(I),{ref_key:"aliasField",ref:c,modelValue:m.value,"onUpdate:modelValue":u[0]||(u[0]=t=>m.value=t),placeholder:s(i)("Start typing a name here..."),hint:s(i)("Alias(es) of this mailbox. To create a catchall alias, just enter the domain name (@domain.tld)."),"persistent-hint":"",loading:p.value,"error-msg":e.value,onDomainSelected:b},null,8,["modelValue","placeholder","hint","loading","error-msg"]),(y(!0),Q(T,null,G(v.value.aliases,(t,r)=>(y(),R(J,{key:r,class:"mr-2 mt-2",closable:"","onClick:close":n=>A(r)},{default:h(()=>[N(x(t),1)]),_:2},1032,["onClick:close"]))),128))]),_:1},512))}};export{xe as A,ye as _,he as a,Ve as b};
|