1
1
"""Admin ViewSets for MailDomain and Mailbox management."""
2
2
3
+ from django .conf import settings
4
+ from django .db .models import Q
3
5
from django .shortcuts import get_object_or_404
4
6
from django .utils .translation import gettext_lazy as _ # For user-facing error messages
5
7
6
- from rest_framework import mixins , status , viewsets
8
+ from drf_spectacular .utils import (
9
+ OpenApiParameter ,
10
+ OpenApiResponse ,
11
+ OpenApiTypes ,
12
+ extend_schema ,
13
+ inline_serializer ,
14
+ )
15
+ from rest_framework import mixins , response , status , viewsets , serializers as drf_serializers
7
16
from rest_framework .response import Response
8
17
9
18
from core import models
10
19
from core .api import permissions as core_permissions
11
20
from core .api import serializers as core_serializers
21
+ from core .identity .keycloak import reset_keycloak_user_password
12
22
13
23
14
- class MailDomainAdminViewSet (mixins .ListModelMixin , viewsets .GenericViewSet ):
24
+ class AdminMailDomainViewSet (mixins .ListModelMixin , viewsets .GenericViewSet ):
15
25
"""
16
26
ViewSet for listing MailDomains the user administers.
17
27
Provides a top-level entry for mail domain administration.
@@ -26,16 +36,13 @@ def get_queryset(self):
26
36
if not user or not user .is_authenticated :
27
37
return models .MailDomain .objects .none ()
28
38
29
- accessible_maildomain_ids = models .MailDomainAccess .objects .filter (
30
- user = user , role = models .MailDomainAccessRoleChoices .ADMIN
31
- ).values_list ("maildomain_id" , flat = True )
32
-
33
39
return models .MailDomain .objects .filter (
34
- id__in = list (accessible_maildomain_ids )
40
+ accesses__user = user ,
41
+ accesses__role = models .MailDomainAccessRoleChoices .ADMIN
35
42
).order_by ("name" )
36
43
37
44
38
- class MailboxAdminViewSet (
45
+ class AdminMailDomainMailboxViewSet (
39
46
mixins .CreateModelMixin ,
40
47
mixins .RetrieveModelMixin ,
41
48
mixins .UpdateModelMixin ,
@@ -65,10 +72,36 @@ def get_queryset(self):
65
72
"local_part"
66
73
)
67
74
75
+ @extend_schema (
76
+ description = "Create new mailbox in a specific maildomain." ,
77
+ request = inline_serializer (
78
+ name = "MailboxAdminCreatePayload" ,
79
+ fields = {
80
+ "local_part" : drf_serializers .CharField (required = True ),
81
+ "alias_of" : drf_serializers .UUIDField (required = False ),
82
+ "metadata" : inline_serializer (
83
+ name = "MailboxAdminCreateMetadata" ,
84
+ fields = {
85
+ "type" : drf_serializers .ChoiceField (choices = ("personal" , "shared" , "redirect" ), required = True ),
86
+ "first_name" : drf_serializers .CharField (required = False , allow_blank = True ),
87
+ "last_name" : drf_serializers .CharField (required = False , allow_blank = True ),
88
+ },
89
+ ),
90
+ },
91
+ ),
92
+ responses = {
93
+ 200 : OpenApiResponse (
94
+ response = core_serializers .MailboxAdminCreateSerializer (),
95
+ description = "The new mailbox with one extra field `one_time_password` if identity provider is keycloak." ,
96
+ ),
97
+ },
98
+ )
68
99
def create (self , request , * args , ** kwargs ):
69
100
maildomain_pk = self .kwargs .get ("maildomain_pk" )
70
101
domain = get_object_or_404 (models .MailDomain , pk = maildomain_pk )
102
+ metadata = request .data .get ("metadata" , {})
71
103
104
+ mailbox_type = metadata .get ("type" )
72
105
local_part = request .data .get ("local_part" )
73
106
alias_of_id = request .data .get ("alias_of" )
74
107
@@ -121,8 +154,86 @@ def create(self, request, *args, **kwargs):
121
154
domain = domain , local_part = local_part , alias_of = alias_of
122
155
)
123
156
157
+ # --- Create user and mailbox access if type is personal ---
158
+ if mailbox_type == "personal" :
159
+ email = f"{ local_part } @{ domain .name } "
160
+ first_name = metadata .get ("first_name" )
161
+ last_name = metadata .get ("last_name" )
162
+ user , _created = models .User .objects .get_or_create (
163
+ email = email ,
164
+ defaults = {
165
+ "full_name" : f"{ first_name } { last_name } " ,
166
+ "short_name" : first_name ,
167
+ "password" : "?" ,
168
+ }
169
+ )
170
+ models .MailboxAccess .objects .create (
171
+ mailbox = mailbox ,
172
+ user = user ,
173
+ role = models .MailboxRoleChoices .ADMIN ,
174
+ )
175
+
124
176
serializer = self .get_serializer (mailbox )
125
177
headers = self .get_success_headers (serializer .data )
178
+ payload = serializer .data
179
+ if mailbox_type == "personal" and settings .IDENTITY_PROVIDER == "keycloak" :
180
+ mailbox_password = reset_keycloak_user_password (email )
181
+ payload ["one_time_password" ] = mailbox_password
126
182
return Response (
127
- serializer . data , status = status .HTTP_201_CREATED , headers = headers
183
+ payload , status = status .HTTP_201_CREATED , headers = headers
128
184
)
185
+
186
+ class AdminMailDomainUserViewSet (mixins .ListModelMixin , viewsets .GenericViewSet ):
187
+ """
188
+ ViewSet for listing users in a specific MailDomain.
189
+ Nested under /maildomains/{maildomain_pk}/users/
190
+ Permissions are checked by IsMailDomainAdmin for the maildomain_pk.
191
+ """
192
+
193
+ permission_classes = [
194
+ core_permissions .IsAuthenticated ,
195
+ core_permissions .IsMailDomainAdmin ,
196
+ ]
197
+ serializer_class = core_serializers .UserSerializer
198
+ pagination_class = None
199
+
200
+ def get_queryset (self ):
201
+ """
202
+ Get all users having an access to a mailbox or an admin access to the maildomain.
203
+ """
204
+ maildomain_pk = self .kwargs .get ("maildomain_pk" )
205
+ # Get all users with an email ending with maildomain.name or with an admin access to the maildomain
206
+ return models .User .objects .filter (
207
+ Q (mailbox_accesses__mailbox__domain_id = maildomain_pk )
208
+ | Q (
209
+ maildomain_accesses__maildomain_id = maildomain_pk ,
210
+ maildomain_accesses__role = models .MailDomainAccessRoleChoices .ADMIN
211
+ )).distinct ().order_by ("full_name" , "short_name" , "email" )
212
+
213
+ @extend_schema (
214
+ tags = ["admin-maildomain-user" ],
215
+ parameters = [
216
+ OpenApiParameter (
217
+ name = "q" ,
218
+ type = OpenApiTypes .STR ,
219
+ location = OpenApiParameter .QUERY ,
220
+ description = "Search maildomains user by full name, short name or email." ,
221
+ ),
222
+ ],
223
+ responses = core_serializers .UserSerializer (many = True ),
224
+ )
225
+ def list (self , request , * args , ** kwargs ):
226
+ """
227
+ Search users by email, first name and last name.
228
+ """
229
+ queryset = self .get_queryset ()
230
+
231
+ if query := request .query_params .get ("q" , "" ):
232
+ queryset = queryset .filter (
233
+ Q (email__unaccent__icontains = query )
234
+ | Q (full_name__unaccent__icontains = query )
235
+ | Q (short_name__unaccent__icontains = query )
236
+ )
237
+
238
+ serializer = core_serializers .UserSerializer (queryset , many = True )
239
+ return response .Response (serializer .data )
0 commit comments