modoboa 2.5.0__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.
Files changed (183) hide show
  1. modoboa/admin/api/v2/serializers.py +1 -1
  2. modoboa/admin/models/domain.py +1 -1
  3. modoboa/admin/models/mixins.py +3 -1
  4. modoboa/amavis/serializers.py +1 -1
  5. modoboa/autoconfig/__init__.py +0 -0
  6. modoboa/autoconfig/apps.py +6 -0
  7. modoboa/autoconfig/templates/autoconfig/autoconfig.xml +24 -0
  8. modoboa/autoconfig/templates/autoconfig/autodiscover.xml +27 -0
  9. modoboa/autoconfig/tests.py +38 -0
  10. modoboa/autoconfig/urls.py +21 -0
  11. modoboa/autoconfig/views.py +109 -0
  12. modoboa/autoreply/api/v2/tests.py +4 -4
  13. modoboa/calendars/tests.py +8 -8
  14. modoboa/contacts/tests.py +5 -7
  15. modoboa/core/api/v1/tests.py +2 -2
  16. modoboa/core/api/v2/views.py +3 -1
  17. modoboa/core/commands/deploy.py +3 -0
  18. modoboa/core/commands/templates/settings.py.tpl +26 -12
  19. modoboa/core/models.py +1 -1
  20. modoboa/core/tests/test_ldap.py +2 -0
  21. modoboa/core/views/auth.py +27 -8
  22. modoboa/frontend_dist/assets/{AccountAliasForm-BuSy_1n9.js → AccountAliasForm-Bu1I8sLg.js} +1 -1
  23. modoboa/frontend_dist/assets/{AccountEditView-qdJmLM_e.js → AccountEditView-YOpDW-Ob.js} +1 -1
  24. modoboa/frontend_dist/assets/AccountLayout-Cf_FVfvJ.js +1 -0
  25. modoboa/frontend_dist/assets/{AccountPasswordSubForm-DZGt_Xgq.js → AccountPasswordSubForm-B9X5wAXp.js} +1 -1
  26. modoboa/frontend_dist/assets/{AccountView-CO65y0vZ.js → AccountView-DCqbD7oi.js} +1 -1
  27. modoboa/frontend_dist/assets/{AddressBook-BZNUlhek.js → AddressBook-B53LXIsD.js} +1 -1
  28. modoboa/frontend_dist/assets/AdminLayout-DX-RXYiU.js +1 -0
  29. modoboa/frontend_dist/assets/AlarmsView-C0KvogOj.js +1 -0
  30. modoboa/frontend_dist/assets/AliasEditView-BiMPygn9.js +1 -0
  31. modoboa/frontend_dist/assets/AliasEditView-CYDSiX8l.css +1 -0
  32. modoboa/frontend_dist/assets/AliasRecipientForm-BhQfATiS.js +1 -0
  33. modoboa/frontend_dist/assets/{AliasView-GOJ5lyQH.js → AliasView-eIct4lMV.js} +1 -1
  34. modoboa/frontend_dist/assets/{AuditTrailView-fbXmq70e.js → AuditTrailView-FiUNTjpE.js} +1 -1
  35. modoboa/frontend_dist/assets/{CalendarView-LlQQNEPL.js → CalendarView-DbWVDoWq.js} +1 -1
  36. modoboa/frontend_dist/assets/{ChoiceField-B3ReQHVe.js → ChoiceField-17aCFpF6.js} +1 -1
  37. modoboa/frontend_dist/assets/{ComposeEmailForm-Bs1fZXAL.js → ComposeEmailForm-B-BP3lzX.js} +1 -1
  38. modoboa/frontend_dist/assets/ComposeEmailView-6JrFhK1W.js +1 -0
  39. modoboa/frontend_dist/assets/ConfirmDialog-Dr0i2kqK.js +1 -0
  40. modoboa/frontend_dist/assets/ConnectedLayout-irTI2ekJ.js +1 -0
  41. modoboa/frontend_dist/assets/{CreationForm-ORg3fazt.js → CreationForm-BZMhQOez.js} +1 -1
  42. modoboa/frontend_dist/assets/DashboardView-CBpsEG3P.js +1 -0
  43. modoboa/frontend_dist/assets/{DomainAdminList-DVn9x0rB.js → DomainAdminList-BikGujRD.js} +1 -1
  44. modoboa/frontend_dist/assets/{DomainEditView-nAoL64D_.js → DomainEditView-SOvfeZWo.js} +1 -1
  45. modoboa/frontend_dist/assets/{DomainTransportForm-CA-DNUxX.js → DomainTransportForm-CmFZ1Cxm.js} +1 -1
  46. modoboa/frontend_dist/assets/{DomainView-CdXPpwJG.js → DomainView-DasOm4jM.js} +3 -3
  47. modoboa/frontend_dist/assets/{DomainsView-B_59gowf.js → DomainsView-DVJA4-oN.js} +1 -1
  48. modoboa/frontend_dist/assets/{EmailField-CwcwI5xW.js → EmailField-DpBYkqam.js} +1 -1
  49. modoboa/frontend_dist/assets/{EmailView-BshxcfAK.js → EmailView-D9vO7HHB.js} +1 -1
  50. modoboa/frontend_dist/assets/EmptyLayout-Dz3F8c7x.js +1 -0
  51. modoboa/frontend_dist/assets/{FiltersView-Cf20MSTK.js → FiltersView-FjdtF4HL.js} +1 -1
  52. modoboa/frontend_dist/assets/ForwardEmailView-DF5BQJjL.js +1 -0
  53. modoboa/frontend_dist/assets/{HtmlEditor-Bh4c689R.js → HtmlEditor-Zn0wjDog.js} +1 -1
  54. modoboa/frontend_dist/assets/{IdentitiesView-BXAuU1YX.js → IdentitiesView-Cf5eQoKA.js} +1 -1
  55. modoboa/frontend_dist/assets/{IdentitiesView-DPrrRMS5.css → IdentitiesView-D3AAiZLZ.css} +1 -1
  56. modoboa/frontend_dist/assets/{InformationView-Cn5FZW7H.js → InformationView-gEYTQ37R.js} +1 -1
  57. modoboa/frontend_dist/assets/{LoadingData-CdVvm4FI.js → LoadingData-Cxd490P6.js} +1 -1
  58. modoboa/frontend_dist/assets/LoginCallbackView-C9zhejHH.js +1 -0
  59. modoboa/frontend_dist/assets/{LoginView-tHIR4Adc.js → LoginView-sWEH834U.js} +1 -1
  60. modoboa/frontend_dist/assets/{MailboxView-B-aI4XBq.css → MailboxView-CfStlWhk.css} +1 -1
  61. modoboa/frontend_dist/assets/MailboxView-Dn3iDym9.js +1 -0
  62. modoboa/frontend_dist/assets/{MenuItems-PXjiG-fs.js → MenuItems-BRChEdLp.js} +1 -1
  63. modoboa/frontend_dist/assets/{MessageView-Cy4STShm.js → MessageView-CDbJxbxm.js} +1 -1
  64. modoboa/frontend_dist/assets/{MessagesView-DdkuEgfX.js → MessagesView-C_vBxVzN.js} +1 -1
  65. modoboa/frontend_dist/assets/{MigrationsView-CidSEjCF.js → MigrationsView-D6peiQuR.js} +1 -1
  66. modoboa/frontend_dist/assets/{ParametersForm-CAv4SH-E.js → ParametersForm-Dip8Sobe.js} +1 -1
  67. modoboa/frontend_dist/assets/ParametersView-CwI0-9kV.js +1 -0
  68. modoboa/frontend_dist/assets/ParametersView-wFKfiEVR.js +1 -0
  69. modoboa/frontend_dist/assets/{ProviderEditView-CrltAQXl.js → ProviderEditView-EH2NSz3x.js} +1 -1
  70. modoboa/frontend_dist/assets/{ProviderGeneralForm-BYAzVnXM.js → ProviderGeneralForm-CAVNOJp6.js} +1 -1
  71. modoboa/frontend_dist/assets/{ProvidersView-osjIY4Ex.js → ProvidersView-BoH99d3a.js} +1 -1
  72. modoboa/frontend_dist/assets/QuarantineLayout--4g9urq2.js +1 -0
  73. modoboa/frontend_dist/assets/{QuarantineView-D8Qg0MXA.js → QuarantineView-BDzDrWwK.js} +1 -1
  74. modoboa/frontend_dist/assets/ReplyEmailView-D0VgYBnw.js +1 -0
  75. modoboa/frontend_dist/assets/ResourcesForm-C1YGkT8i.js +1 -0
  76. modoboa/frontend_dist/assets/SelfServiceLayout-BYkSLc4n.js +1 -0
  77. modoboa/frontend_dist/assets/{SettingsView-9iNcDhkI.js → SettingsView-Cy5O-88Y.js} +1 -1
  78. modoboa/frontend_dist/assets/{StatisticsView-cHsPyGkL.js → StatisticsView-DtlBQK9z.js} +1 -1
  79. modoboa/frontend_dist/assets/{TimeSerieChart--V83dcJ9.js → TimeSerieChart-DNjvmMDE.js} +1 -1
  80. modoboa/frontend_dist/assets/{TimeSerieChart-CxiwMzE8.css → TimeSerieChart-Dn6Wznn8.css} +1 -1
  81. modoboa/frontend_dist/assets/UserLayout-Cgqq7HVi.js +1 -0
  82. modoboa/frontend_dist/assets/{VAlert-BuaaYN2h.js → VAlert-x6n3drjw.js} +1 -1
  83. modoboa/frontend_dist/assets/{VApp-CKP-6zGP.js → VApp-C74A9rX5.js} +1 -1
  84. modoboa/frontend_dist/assets/{VAutocomplete-Dwv6_Rzq.js → VAutocomplete-Bg2AlW_n.js} +1 -1
  85. modoboa/frontend_dist/assets/{VAvatar-Cmga0vj6.js → VAvatar-J563boKh.js} +1 -1
  86. modoboa/frontend_dist/assets/{VBadge-CixeK87a.js → VBadge-CtEsXuih.js} +1 -1
  87. modoboa/frontend_dist/assets/{VCard-CxH9DWoK.js → VCard-CJ2oGh_i.js} +1 -1
  88. modoboa/frontend_dist/assets/{VCheckbox-62GOpvvP.js → VCheckbox-VicuXIJq.js} +1 -1
  89. modoboa/frontend_dist/assets/{VCheckboxBtn-DMoNtKT8.js → VCheckboxBtn-CLh3m66z.js} +1 -1
  90. modoboa/frontend_dist/assets/{VChip-D_styETR.js → VChip-DvxO74Mw.js} +1 -1
  91. modoboa/frontend_dist/assets/VColorPicker-DcVohVWE.js +1 -0
  92. modoboa/frontend_dist/assets/{VContainer-B46caNs1.js → VContainer-L2EoH3dT.js} +1 -1
  93. modoboa/frontend_dist/assets/{VDataTable-Bh8NbVSx.js → VDataTable-CAT0QYJz.js} +1 -1
  94. modoboa/frontend_dist/assets/{VDataTableServer-BDR5hOmo.js → VDataTableServer-DP13Fhfa.js} +1 -1
  95. modoboa/frontend_dist/assets/{VDataTableVirtual-BOQlNtIG.js → VDataTableVirtual-LVNemzH7.js} +1 -1
  96. modoboa/frontend_dist/assets/{VDialog-BcTg7w6P.js → VDialog-CaQnQSDn.js} +1 -1
  97. modoboa/frontend_dist/assets/{VExpansionPanels-BmH5Jl2Z.js → VExpansionPanels-Dnwgt5uT.js} +1 -1
  98. modoboa/frontend_dist/assets/{VFileInput-BC4yAygd.js → VFileInput-z44hJIWr.js} +1 -1
  99. modoboa/frontend_dist/assets/{VForm-D5iPGkde.js → VForm-BwTUBf4u.js} +1 -1
  100. modoboa/frontend_dist/assets/{VInput-CoDJzvaW.js → VInput-Dpmea5z9.js} +1 -1
  101. modoboa/frontend_dist/assets/{VMenu-gUG70-zD.js → VMenu-DDmBlbM8.js} +1 -1
  102. modoboa/frontend_dist/assets/{VPicker-BXuKT3zB.js → VPicker-Dz7GbXwU.js} +1 -1
  103. modoboa/frontend_dist/assets/{VProgressCircular-BtOPiGCg.js → VProgressCircular-CtqQON49.js} +1 -1
  104. modoboa/frontend_dist/assets/{VRadioGroup-DIFZKSn-.js → VRadioGroup-WW8uMAv4.js} +1 -1
  105. modoboa/frontend_dist/assets/{VRow-ozg66L7j.js → VRow-CAbOSEHS.js} +1 -1
  106. modoboa/frontend_dist/assets/{VSelect-C3RjAa45.js → VSelect-Cdh0qO5m.js} +1 -1
  107. modoboa/frontend_dist/assets/{VSelectionControl-zyz-fJvC.js → VSelectionControl-BN-zQ3C6.js} +1 -1
  108. modoboa/frontend_dist/assets/{VSheet-BNx2X4Mk.js → VSheet-9RDZpWCf.js} +1 -1
  109. modoboa/frontend_dist/assets/VSpacer-BWZq1PhD.js +1 -0
  110. modoboa/frontend_dist/assets/{VSwitch-DwxdeAEq.js → VSwitch-CixaedmQ.js} +1 -1
  111. modoboa/frontend_dist/assets/{VTable-DaLxa4FO.js → VTable-DO7ofPSj.js} +1 -1
  112. modoboa/frontend_dist/assets/{VTabs-BP0Hgsgm.js → VTabs-C1-E1DJE.js} +1 -1
  113. modoboa/frontend_dist/assets/{VTextField-XoGTj1KG.js → VTextField-C9VUP7V9.js} +1 -1
  114. modoboa/frontend_dist/assets/{VTextarea-wBlRMIv_.js → VTextarea-B28SmJ2v.js} +1 -1
  115. modoboa/frontend_dist/assets/{VToolbar-CFZfqeOr.js → VToolbar-Cs-fVa4M.js} +1 -1
  116. modoboa/frontend_dist/assets/VWindowItem-C9PFEbE6.js +1 -0
  117. modoboa/frontend_dist/assets/WebmailLayout-Dp6jIazW.js +1 -0
  118. modoboa/frontend_dist/assets/{accounts-DUzbx6k8.js → accounts-BT429O7P.js} +1 -1
  119. modoboa/frontend_dist/assets/{admin-DewTk2H8.js → admin-Cu5rjY7m.js} +1 -1
  120. modoboa/frontend_dist/assets/{aliases-4sXmjwXp.js → aliases-Q4uTcEh4.js} +1 -1
  121. modoboa/frontend_dist/assets/{amavis-CC0li7_T.js → amavis-Be87kCqw.js} +1 -1
  122. modoboa/frontend_dist/assets/{amavis-DK8SHE6o.js → amavis-Bx0pxvRn.js} +1 -1
  123. modoboa/frontend_dist/assets/{contacts-BjghrPqZ.js → contacts-Dx240BT8.js} +1 -1
  124. modoboa/frontend_dist/assets/{domains-BSawReeu.js → domains-CQv8gtbQ.js} +1 -1
  125. modoboa/frontend_dist/assets/{domains.store-D-vWCEIK.js → domains.store-CfpnnBGo.js} +1 -1
  126. modoboa/frontend_dist/assets/{filter-C82FUCw_.js → filter-C_GJovn5.js} +1 -1
  127. modoboa/frontend_dist/assets/{forwardRefs-cvcnlhoK.js → forwardRefs-DPrLRqxP.js} +1 -1
  128. modoboa/frontend_dist/assets/{global.store-DbkcI5o2.js → global.store-CNChIxQL.js} +1 -1
  129. modoboa/frontend_dist/assets/importExport-7sRHU_3R.js +1 -0
  130. modoboa/frontend_dist/assets/index-YIGRKgNA.js +982 -0
  131. modoboa/frontend_dist/assets/{layout-C5FyYCHK.js → layout-BM4C5b2r.js} +1 -1
  132. modoboa/frontend_dist/assets/{layout.store-NXWtFIwL.js → layout.store-CCOp6tv6.js} +1 -1
  133. modoboa/frontend_dist/assets/{logos-BswdveCV.js → logos-CboXciB_.js} +1 -1
  134. modoboa/frontend_dist/assets/{logs-6CbtfaZS.js → logs-BeuijmJA.js} +1 -1
  135. modoboa/frontend_dist/assets/{parameters-aSQiR7kN.js → parameters-DpVw-Zs4.js} +1 -1
  136. modoboa/frontend_dist/assets/{parameters.store-CzQqVatx.js → parameters.store-DRarkO6b.js} +1 -1
  137. modoboa/frontend_dist/assets/{permissions-DNoefz-n.js → permissions-C33K6cJv.js} +1 -1
  138. modoboa/frontend_dist/assets/{ssrBoot-CKUX4kcb.js → ssrBoot-CpJ-7WlH.js} +1 -1
  139. modoboa/frontend_dist/assets/{tag-B_yWNNJD.js → tag-DV1_zOzl.js} +1 -1
  140. modoboa/frontend_dist/assets/theme-BGzdVPko.js +1 -0
  141. modoboa/frontend_dist/assets/transports-B9mWU1W8.js +1 -0
  142. modoboa/frontend_dist/assets/{webmail-CdU6CD9b.js → webmail-DRyN8JIK.js} +1 -1
  143. modoboa/frontend_dist/index.html +1 -1
  144. modoboa/static/css/offline.css +7 -20
  145. modoboa/templates/registration/base.html +18 -0
  146. modoboa/templates/registration/login.html +1 -1
  147. modoboa/urls.py +1 -0
  148. modoboa/webmail/lib/imaputils.py +2 -2
  149. {modoboa-2.5.0.dist-info → modoboa-2.6.0.dist-info}/METADATA +6 -6
  150. {modoboa-2.5.0.dist-info → modoboa-2.6.0.dist-info}/RECORD +155 -147
  151. {modoboa-2.5.0.dist-info → modoboa-2.6.0.dist-info}/licenses/LICENSE +1 -1
  152. modoboa/frontend_dist/assets/AccountLayout-DrN7vHsX.js +0 -1
  153. modoboa/frontend_dist/assets/AdminLayout-CTNhuwTw.js +0 -1
  154. modoboa/frontend_dist/assets/AlarmsView-DN_JIw9g.js +0 -1
  155. modoboa/frontend_dist/assets/AliasEditView-Cx9410JP.css +0 -1
  156. modoboa/frontend_dist/assets/AliasEditView-DjpPUTp9.js +0 -1
  157. modoboa/frontend_dist/assets/AliasRecipientForm-B1Y8wFdP.js +0 -1
  158. modoboa/frontend_dist/assets/ComposeEmailView-s3LMl3pO.js +0 -1
  159. modoboa/frontend_dist/assets/ConfirmDialog-DY_kUHLG.js +0 -1
  160. modoboa/frontend_dist/assets/ConnectedLayout-UWjiYBNw.js +0 -1
  161. modoboa/frontend_dist/assets/DashboardView-Dplk9itS.js +0 -1
  162. modoboa/frontend_dist/assets/EmptyLayout-DFfhnhLi.js +0 -1
  163. modoboa/frontend_dist/assets/ForwardEmailView-CZG062os.js +0 -1
  164. modoboa/frontend_dist/assets/LoginCallbackView-B9hAH4MI.js +0 -1
  165. modoboa/frontend_dist/assets/MailboxView-Bugu2vhg.js +0 -1
  166. modoboa/frontend_dist/assets/ParametersView-CX7Ffemw.js +0 -1
  167. modoboa/frontend_dist/assets/ParametersView-CrbNcmV3.js +0 -1
  168. modoboa/frontend_dist/assets/QuarantineLayout-B8EcU9vS.js +0 -1
  169. modoboa/frontend_dist/assets/ReplyEmailView-BABPqWhd.js +0 -1
  170. modoboa/frontend_dist/assets/ResourcesForm-OaqdRYVs.js +0 -1
  171. modoboa/frontend_dist/assets/SelfServiceLayout-d277YTGR.js +0 -1
  172. modoboa/frontend_dist/assets/UserLayout-B3sBiTcZ.js +0 -1
  173. modoboa/frontend_dist/assets/VColorPicker-BHscBGQV.js +0 -1
  174. modoboa/frontend_dist/assets/VSpacer-DinPiXs9.js +0 -1
  175. modoboa/frontend_dist/assets/VWindowItem-BB7ETW3b.js +0 -1
  176. modoboa/frontend_dist/assets/WebmailLayout-_Hk1XhVq.js +0 -1
  177. modoboa/frontend_dist/assets/importExport-DzoL4Mvc.js +0 -1
  178. modoboa/frontend_dist/assets/index-BImkz5Jx.js +0 -984
  179. modoboa/frontend_dist/assets/transports-BDNB9wR5.js +0 -1
  180. {modoboa-2.5.0.data → modoboa-2.6.0.data}/scripts/modoboa-admin.py +0 -0
  181. {modoboa-2.5.0.dist-info → modoboa-2.6.0.dist-info}/WHEEL +0 -0
  182. {modoboa-2.5.0.dist-info → modoboa-2.6.0.dist-info}/entry_points.txt +0 -0
  183. {modoboa-2.5.0.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):
@@ -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
@@ -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
@@ -10,7 +10,7 @@ from modoboa.amavis import models
10
10
  class GlobalParametersSerializer(serializers.Serializer):
11
11
 
12
12
  localpart_is_case_sensitive = serializers.BooleanField(default=False)
13
- recipient_delimiter = serializers.CharField(default="")
13
+ recipient_delimiter = serializers.CharField(default="", allow_blank=True)
14
14
  max_messages_age = serializers.IntegerField(default=14)
15
15
  released_msgs_cleanup = serializers.BooleanField(default=False)
16
16
  am_pdp_mode = serializers.ChoiceField(
File without changes
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class AutoconfigConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "modoboa.autoconfig"
@@ -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.force_login(admin)
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.force_login(self.account)
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.force_login(self.account)
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.force_login(self.account)
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)
@@ -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 ModoTestCase, ModoAPITestCase
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(ModoTestCase):
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.force_login(self.account)
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.force_login(self.admin_account)
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.force_login(self.account)
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.force_login(self.account)
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.force_login(self.account)
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.force_login(self.account)
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, ModoTestCase
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.force_login(self.user)
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
- data = {"username": self.user.username, "password": "toto"}
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
- data = {"username": user.username, "password": "toto"}
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, ModoTestCase):
313
+ class ImportTestCase(TestDataMixin, ModoAPITestCase):
316
314
 
317
315
  def setUp(self):
318
316
  super().setUp()
@@ -1,9 +1,9 @@
1
1
  from django.urls import reverse_lazy
2
2
 
3
- from modoboa.lib.tests import ModoTestCase
3
+ from modoboa.lib.tests import ModoAPITestCase
4
4
 
5
5
 
6
- class OpenAPITestCase(ModoTestCase):
6
+ class OpenAPITestCase(ModoAPITestCase):
7
7
  openapi_schema_url = reverse_lazy("schema-v1-legacy")
8
8
 
9
9
  def test_unauthorized(self):
@@ -157,7 +157,9 @@ class ComponentsInformationAPIView(APIView):
157
157
  class NotificationsAPIView(APIView):
158
158
  """Return list of active notifications."""
159
159
 
160
- permission_classes = [permissions.IsAuthenticated, IsSuperUser]
160
+ permission_classes = [
161
+ permissions.IsAuthenticated,
162
+ ]
161
163
  throttle_classes = [UserLesserDdosUser]
162
164
 
163
165
  @extend_schema(responses=serializers.NotificationSerializer(many=True))
@@ -218,6 +218,7 @@ class DeployCommand(Command):
218
218
  allowed_host = "localhost"
219
219
  extra_settings = []
220
220
  extensions = parsed_args.extensions
221
+ amavis_enabled = False
221
222
  if extensions:
222
223
  if "all" in extensions:
223
224
  extensions = self._get_extension_list()
@@ -233,6 +234,7 @@ class DeployCommand(Command):
233
234
  exec_cmd(cmd, capture_output=False)
234
235
  extra_settings = self.find_extra_settings(extensions)
235
236
  extensions = [extension[1] for extension in extensions]
237
+ amavis_enabled = "modoboa.amavis" in extensions
236
238
 
237
239
  mod = __import__(parsed_args.name, globals(), locals(), [smart_str("settings")])
238
240
  tpl = self._render_template(
@@ -247,6 +249,7 @@ class DeployCommand(Command):
247
249
  "devmode": parsed_args.devel,
248
250
  "extensions": extensions,
249
251
  "extra_settings": extra_settings,
252
+ "amavis_enabled": amavis_enabled,
250
253
  },
251
254
  )
252
255
  with open(f"{path}/settings.py", "w") as fp:
@@ -2,10 +2,10 @@
2
2
  Django settings for {{ name }} project.
3
3
 
4
4
  For more information on this file, see
5
- https://docs.djangoproject.com/en/4.2/topics/settings/
5
+ https://docs.djangoproject.com/en/dev/topics/settings/
6
6
 
7
7
  For the full list of settings and their values, see
8
- https://docs.djangoproject.com/en/2.2/ref/settings/
8
+ https://docs.djangoproject.com/en/dev/ref/settings/
9
9
  """
10
10
 
11
11
  from logging.handlers import SysLogHandler
@@ -17,9 +17,6 @@ env = environ.Env()
17
17
  BASE_DIR = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
18
18
  environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
19
19
 
20
- # Quick-start development settings - unsuitable for production
21
- # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
22
-
23
20
  # SECURITY WARNING: keep the secret key used in production secret!
24
21
  SECRET_KEY = '{{ secret_key }}'
25
22
 
@@ -41,6 +38,19 @@ SITE_ID = 1
41
38
  # The email address that error messages come from, such as those sent to ADMINS
42
39
  #SERVER_EMAIL = 'webmaster@example.net'
43
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
+
44
54
  # Security settings
45
55
 
46
56
  X_FRAME_OPTIONS = "SAMEORIGIN"
@@ -78,6 +88,7 @@ MODOBOA_APPS = (
78
88
  'modoboa.core',
79
89
  'modoboa.lib',
80
90
  'modoboa.admin',
91
+ 'modoboa.autoconfig',
81
92
  'modoboa.transport',
82
93
  'modoboa.relaydomains',
83
94
  'modoboa.limits',
@@ -157,14 +168,14 @@ WSGI_APPLICATION = '{{ name }}.wsgi.application'
157
168
 
158
169
 
159
170
  # Database
160
- # https://docs.djangoproject.com/en/4.2/ref/settings/#databases
171
+ # https://docs.djangoproject.com/en/dev/ref/settings/#databases
161
172
 
162
173
  DATABASES = {
163
174
  {% for conn in db_connections.values %}{{ conn|safe }}{% endfor %}
164
175
  }
165
176
 
166
177
  # Internationalization
167
- # https://docs.djangoproject.com/en/4.2/topics/i18n/
178
+ # https://docs.djangoproject.com/en/dev/topics/i18n/
168
179
 
169
180
  LANGUAGE_CODE = '{{ lang }}'
170
181
 
@@ -177,12 +188,12 @@ USE_L10N = True
177
188
  USE_TZ = True
178
189
 
179
190
  # Default primary key field type
180
- # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
191
+ # https://docs.djangoproject.com/en/dev/ref/settings/#default-auto-field
181
192
 
182
193
  DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
183
194
 
184
195
  # Static files (CSS, JavaScript, Images)
185
- # https://docs.djangoproject.com/en/2.2/howto/static-files/
196
+ # https://docs.djangoproject.com/en/dev/howto/static-files/
186
197
 
187
198
  STATIC_URL = '/sitestatic/'
188
199
  STATIC_ROOT = os.path.join(BASE_DIR, 'sitestatic')
@@ -224,7 +235,6 @@ REST_FRAMEWORK = {
224
235
  'DEFAULT_AUTHENTICATION_CLASSES': (
225
236
  'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
226
237
  'rest_framework.authentication.TokenAuthentication',
227
- 'rest_framework.authentication.SessionAuthentication',
228
238
  ),
229
239
  'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
230
240
  'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
@@ -295,7 +305,7 @@ CACHES = {
295
305
 
296
306
 
297
307
  # Password validation
298
- # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
308
+ # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
299
309
 
300
310
  AUTH_PASSWORD_VALIDATORS = [
301
311
  {
@@ -384,7 +394,11 @@ SILENCED_SYSTEM_CHECKS = [
384
394
  ]
385
395
 
386
396
  PHONENUMBER_DB_FORMAT = 'INTERNATIONAL'
387
-
397
+ {% if amavis_enabled %}
398
+ # Amavis settings
399
+ DATABASE_ROUTERS = ["modoboa.amavis.dbrouter.AmavisRouter"]
400
+ AMAVIS_DEFAULT_DATABASE_ENCODING = "LATIN1" # or any value matching your database config
401
+ {% endif %}
388
402
  # Load settings from extensions
389
403
  {% for extension in extra_settings %}
390
404
  try:
modoboa/core/models.py CHANGED
@@ -220,7 +220,7 @@ class User(AbstractUser):
220
220
  return self.email
221
221
 
222
222
  @property
223
- def tfa_enabled(self):
223
+ def tfa_enabled(self) -> bool:
224
224
  return self.totp_enabled or self.webauthn_enabled
225
225
 
226
226
  def is_owner(self, obj):
@@ -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"},