blob: 7dfa983c85ca6f6f49bece2a32ff79b382e4eed5 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""A class to display a user group, including a paginated list of members."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
10import logging
11import time
12
13import ezt
14
15from framework import exceptions
16from framework import framework_helpers
17from framework import framework_views
18from framework import paginate
19from framework import permissions
20from framework import servlet
21from project import project_helpers
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010022from mrproto import usergroup_pb2
Copybara854996b2021-09-07 19:36:02 +000023from sitewide import group_helpers
24from sitewide import sitewide_views
25
26MEMBERS_PER_PAGE = 50
27
28
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010029class GroupDetail(servlet.Servlet):
Copybara854996b2021-09-07 19:36:02 +000030 """The group detail page presents information about one user group."""
31
32 _PAGE_TEMPLATE = 'sitewide/group-detail-page.ezt'
33
34 def AssertBasePermission(self, mr):
35 """Assert that the user has the permissions needed to view this page."""
36 super(GroupDetail, self).AssertBasePermission(mr)
37
38 group_id = mr.viewed_user_auth.user_id
39 group_settings = self.services.usergroup.GetGroupSettings(
40 mr.cnxn, group_id)
41 if not group_settings:
42 return
43
44 member_ids, owner_ids = self.services.usergroup.LookupAllMembers(
45 mr.cnxn, [group_id])
46 (owned_project_ids, membered_project_ids,
47 contrib_project_ids) = self.services.project.GetUserRolesInAllProjects(
48 mr.cnxn, mr.auth.effective_ids)
49 project_ids = owned_project_ids.union(
50 membered_project_ids).union(contrib_project_ids)
51 if not permissions.CanViewGroupMembers(
52 mr.perms, mr.auth.effective_ids, group_settings, member_ids[group_id],
53 owner_ids[group_id], project_ids):
54 raise permissions.PermissionException(
55 'User is not allowed to view a user group')
56
57 def GatherPageData(self, mr):
58 """Build up a dictionary of data values to use when rendering the page."""
59 group_id = mr.viewed_user_auth.user_id
60 group_settings = self.services.usergroup.GetGroupSettings(
61 mr.cnxn, group_id)
62 if not group_settings:
63 raise exceptions.NoSuchGroupException()
64
65 member_ids_dict, owner_ids_dict = (
66 self.services.usergroup.LookupVisibleMembers(
67 mr.cnxn, [group_id], mr.perms, mr.auth.effective_ids,
68 self.services))
69 member_ids = member_ids_dict[group_id]
70 owner_ids = owner_ids_dict[group_id]
71 member_pbs_dict = self.services.user.GetUsersByIDs(
72 mr.cnxn, member_ids)
73 owner_pbs_dict = self.services.user.GetUsersByIDs(
74 mr.cnxn, owner_ids)
75 member_dict = {}
76 for user_id, user_pb in member_pbs_dict.items():
77 member_view = group_helpers.GroupMemberView(user_pb, group_id, 'member')
78 member_dict[user_id] = member_view
79 owner_dict = {}
80 for user_id, user_pb in owner_pbs_dict.items():
81 member_view = group_helpers.GroupMemberView(user_pb, group_id, 'owner')
82 owner_dict[user_id] = member_view
83
84 member_user_views = []
85 member_user_views.extend(
86 sorted(list(owner_dict.values()), key=lambda u: u.email))
87 member_user_views.extend(
88 sorted(list(member_dict.values()), key=lambda u: u.email))
89
90 group_view = sitewide_views.GroupView(
91 mr.viewed_user_auth.email, len(member_ids), group_settings,
92 mr.viewed_user_auth.user_id)
93 url_params = [(name, mr.GetParam(name)) for name in
94 framework_helpers.RECOGNIZED_PARAMS]
95 pagination = paginate.ArtifactPagination(
96 member_user_views, mr.GetPositiveIntParam('num', MEMBERS_PER_PAGE),
97 mr.GetPositiveIntParam('start'), mr.project_name, group_view.detail_url,
98 url_params=url_params)
99
100 is_imported_group = bool(group_settings.ext_group_type)
101
102 offer_membership_editing = permissions.CanEditGroup(
103 mr.perms, mr.auth.effective_ids, owner_ids) and not is_imported_group
104
105 group_type = 'Monorail user group'
106 if group_settings.ext_group_type:
107 group_type = str(group_settings.ext_group_type).capitalize()
108
109 return {
110 'admin_tab_mode': self.ADMIN_TAB_META,
111 'offer_membership_editing': ezt.boolean(offer_membership_editing),
112 'initial_add_members': '',
113 'initially_expand_form': ezt.boolean(False),
114 'groupid': group_id,
115 'groupname': mr.viewed_username,
116 'settings': group_settings,
117 'group_type': group_type,
118 'pagination': pagination,
119 }
120
121 def ProcessFormData(self, mr, post_data):
122 """Process the posted form."""
123 _, owner_ids_dict = self.services.usergroup.LookupMembers(
124 mr.cnxn, [mr.viewed_user_auth.user_id])
125 owner_ids = owner_ids_dict[mr.viewed_user_auth.user_id]
126 permit_edit = permissions.CanEditGroup(
127 mr.perms, mr.auth.effective_ids, owner_ids)
128 if not permit_edit:
129 raise permissions.PermissionException(
130 'User is not permitted to edit group membership')
131
132 group_settings = self.services.usergroup.GetGroupSettings(
133 mr.cnxn, mr.viewed_user_auth.user_id)
134 if bool(group_settings.ext_group_type):
135 raise permissions.PermissionException(
136 'Imported groups are read-only')
137
138 if 'addbtn' in post_data:
139 return self.ProcessAddMembers(mr, post_data)
140 elif 'removebtn' in post_data:
141 return self.ProcessRemoveMembers(mr, post_data)
142
143 def ProcessAddMembers(self, mr, post_data):
144 """Process the user's request to add members.
145
146 Args:
147 mr: common information parsed from the HTTP request.
148 post_data: dictionary of form data.
149
150 Returns:
151 String URL to redirect the user to after processing.
152 """
153 # 1. Gather data from the request.
154 group_id = mr.viewed_user_auth.user_id
155 add_members_str = post_data.get('addmembers')
156 new_member_ids = project_helpers.ParseUsernames(
157 mr.cnxn, self.services.user, add_members_str)
158 role = post_data['role']
159
160 # 2. Call services layer to save changes.
161 if not mr.errors.AnyErrors():
162 try:
163 self.services.usergroup.UpdateMembers(
164 mr.cnxn, group_id, new_member_ids, role)
165 except exceptions.CircularGroupException:
166 mr.errors.addmembers = (
167 'The members are already ancestors of current group.')
168
169 # 3. Determine the next page in the UI flow.
170 if mr.errors.AnyErrors():
171 self.PleaseCorrect(
172 mr, initial_add_members=add_members_str,
173 initially_expand_form=ezt.boolean(True))
174 else:
175 return framework_helpers.FormatAbsoluteURL(
176 mr, '/g/%s/' % mr.viewed_username, include_project=False,
177 saved=1, ts=int(time.time()))
178
179 def ProcessRemoveMembers(self, mr, post_data):
180 """Process the user's request to remove members.
181
182 Args:
183 mr: common information parsed from the HTTP request.
184 post_data: dictionary of form data.
185
186 Returns:
187 String URL to redirect the user to after processing.
188 """
189 # 1. Gather data from the request.
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200190 remove_strs = post_data.getlist('remove')
Copybara854996b2021-09-07 19:36:02 +0000191 logging.info('remove_strs = %r', remove_strs)
192
193 if not remove_strs:
194 mr.errors.remove = 'No users specified'
195
196 # 2. Call services layer to save changes.
197 if not mr.errors.AnyErrors():
198 remove_ids = set(
199 self.services.user.LookupUserIDs(mr.cnxn, remove_strs).values())
200 self.services.usergroup.RemoveMembers(
201 mr.cnxn, mr.viewed_user_auth.user_id, remove_ids)
202
203 # 3. Determine the next page in the UI flow.
204 if mr.errors.AnyErrors():
205 self.PleaseCorrect(mr)
206 else:
207 return framework_helpers.FormatAbsoluteURL(
208 mr, '/g/%s/' % mr.viewed_username, include_project=False,
209 saved=1, ts=int(time.time()))
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200210
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200211 def GetGroupDetail(self, **kwargs):
212 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200213
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200214 def PostGroupDetail(self, **kwargs):
215 return self.handler(**kwargs)