blob: 33045c49f00fa6b8e128b0dbffcd0c57041ffc2b [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"""Classes for the user profile page ("my page")."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
Copybara854996b2021-09-07 19:36:02 +000010import time
11import json
12
13import ezt
14
15import settings
16from businesslogic import work_env
17from framework import framework_helpers
18from framework import framework_views
19from framework import permissions
20from framework import servlet
21from framework import timestr
22from framework import xsrf
23from project import project_views
Copybara854996b2021-09-07 19:36:02 +000024
25
26class UserProfile(servlet.Servlet):
27 """Shows a page of information about a user."""
28
29 _PAGE_TEMPLATE = 'sitewide/user-profile-page.ezt'
30
31 def GatherPageData(self, mr):
32 """Build up a dictionary of data values to use when rendering the page."""
33 viewed_user = mr.viewed_user_auth.user_pb
34 if self.services.usergroup.GetGroupSettings(
35 mr.cnxn, mr.viewed_user_auth.user_id):
36 url = framework_helpers.FormatAbsoluteURL(
37 mr, '/g/%s/' % viewed_user.email, include_project=False)
38 self.redirect(url, abort=True) # Show group page instead.
39
40 with work_env.WorkEnv(mr, self.services) as we:
41 project_lists = we.GetUserProjects(mr.viewed_user_auth.effective_ids)
42
43 (visible_ownership, visible_archived, visible_membership,
44 visible_contrib) = project_lists
45
46 with mr.profiler.Phase('Getting user groups'):
47 group_settings = self.services.usergroup.GetAllGroupSettings(
48 mr.cnxn, mr.viewed_user_auth.effective_ids)
49 member_ids, owner_ids = self.services.usergroup.LookupAllMembers(
50 mr.cnxn, list(group_settings.keys()))
51 friend_project_ids = [] # TODO(issue 4202): implement this.
52 visible_group_ids = []
53 for group_id in group_settings:
54 if permissions.CanViewGroupMembers(
55 mr.perms, mr.auth.effective_ids, group_settings[group_id],
56 member_ids[group_id], owner_ids[group_id], friend_project_ids):
57 visible_group_ids.append(group_id)
58
59 user_group_views = framework_views.MakeAllUserViews(
60 mr.cnxn, self.services.user, visible_group_ids)
61 user_group_views = sorted(
62 list(user_group_views.values()), key=lambda ugv: ugv.email)
63
64 with mr.profiler.Phase('Getting linked accounts'):
65 linked_parent = None
66 linked_children = []
67 linked_views = framework_views.MakeAllUserViews(
68 mr.cnxn, self.services.user,
69 [viewed_user.linked_parent_id],
70 viewed_user.linked_child_ids)
71 if viewed_user.linked_parent_id:
72 linked_parent = linked_views[viewed_user.linked_parent_id]
73 if viewed_user.linked_child_ids:
74 linked_children = [
75 linked_views[child_id] for child_id in viewed_user.linked_child_ids]
76 offer_unlink = (mr.auth.user_id == viewed_user.user_id or
77 mr.auth.user_id in linked_views)
78
79 incoming_invite_users = []
80 outgoing_invite_users = []
81 possible_parent_accounts = []
82 can_edit_invites = mr.auth.user_id == mr.viewed_user_auth.user_id
83 display_link_invites = can_edit_invites or mr.auth.user_pb.is_site_admin
84 # TODO(jrobbins): allow site admin to edit invites for other users.
85 if display_link_invites:
86 with work_env.WorkEnv(mr, self.services, phase='Getting link invites'):
87 incoming_invite_ids, outgoing_invite_ids = we.GetPendingLinkedInvites(
88 user_id=viewed_user.user_id)
89 invite_views = framework_views.MakeAllUserViews(
90 mr.cnxn, self.services.user, incoming_invite_ids, outgoing_invite_ids)
91 incoming_invite_users = [
92 invite_views[uid] for uid in incoming_invite_ids]
93 outgoing_invite_users = [
94 invite_views[uid] for uid in outgoing_invite_ids]
95 possible_parent_accounts = _ComputePossibleParentAccounts(
96 we, mr.viewed_user_auth.user_view, linked_parent, linked_children)
97
98 viewed_user_display_name = framework_views.GetViewedUserDisplayName(mr)
99
100 with work_env.WorkEnv(mr, self.services) as we:
101 starred_projects = we.ListStarredProjects(
102 viewed_user_id=mr.viewed_user_auth.user_id)
103 logged_in_starred = we.ListStarredProjects()
104 logged_in_starred_pids = {p.project_id for p in logged_in_starred}
105
106 starred_user_ids = self.services.user_star.LookupStarredItemIDs(
107 mr.cnxn, mr.viewed_user_auth.user_id)
108 starred_user_dict = framework_views.MakeAllUserViews(
109 mr.cnxn, self.services.user, starred_user_ids)
110 starred_users = list(starred_user_dict.values())
111 starred_users_json = json.dumps(
112 [uv.display_name for uv in starred_users])
113
114 is_user_starred = self._IsUserStarred(
115 mr.cnxn, mr.auth.user_id, mr.viewed_user_auth.user_id)
116
117 if viewed_user.last_visit_timestamp:
118 last_visit_str = timestr.FormatRelativeDate(
119 viewed_user.last_visit_timestamp, days_only=True)
120 last_visit_str = last_visit_str or 'Less than 2 days ago'
121 else:
122 last_visit_str = 'Never'
123
124 if viewed_user.email_bounce_timestamp:
125 last_bounce_str = timestr.FormatRelativeDate(
126 viewed_user.email_bounce_timestamp, days_only=True)
127 last_bounce_str = last_bounce_str or 'Less than 2 days ago'
128 else:
129 last_bounce_str = None
130
131 can_ban = permissions.CanBan(mr, self.services)
132 viewed_user_is_spammer = viewed_user.banned.lower() == 'spam'
133 viewed_user_may_be_spammer = not viewed_user_is_spammer
134 all_projects = self.services.project.GetAllProjects(mr.cnxn)
135 for project_id in all_projects:
136 project = all_projects[project_id]
137 viewed_user_perms = permissions.GetPermissions(viewed_user,
138 mr.viewed_user_auth.effective_ids, project)
139 if (viewed_user_perms != permissions.EMPTY_PERMISSIONSET and
140 viewed_user_perms != permissions.USER_PERMISSIONSET):
141 viewed_user_may_be_spammer = False
142
143 ban_token = None
144 ban_spammer_token = None
145 if mr.auth.user_id and can_ban:
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200146 form_token_path = mr.request_path + 'ban.do'
Copybara854996b2021-09-07 19:36:02 +0000147 ban_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200148 form_token_path = mr.request_path + 'banSpammer.do'
Copybara854996b2021-09-07 19:36:02 +0000149 ban_spammer_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path)
150
151 can_delete_user = permissions.CanExpungeUsers(mr)
152
153 page_data = {
154 'user_tab_mode': 'st2',
155 'viewed_user_display_name': viewed_user_display_name,
156 'viewed_user_may_be_spammer': ezt.boolean(viewed_user_may_be_spammer),
157 'viewed_user_is_spammer': ezt.boolean(viewed_user_is_spammer),
158 'viewed_user_is_banned': ezt.boolean(viewed_user.banned),
159 'owner_of_projects': [
160 project_views.ProjectView(
161 p, starred=p.project_id in logged_in_starred_pids)
162 for p in visible_ownership],
163 'committer_of_projects': [
164 project_views.ProjectView(
165 p, starred=p.project_id in logged_in_starred_pids)
166 for p in visible_membership],
167 'contributor_to_projects': [
168 project_views.ProjectView(
169 p, starred=p.project_id in logged_in_starred_pids)
170 for p in visible_contrib],
171 'owner_of_archived_projects': [
172 project_views.ProjectView(p) for p in visible_archived],
173 'starred_projects': [
174 project_views.ProjectView(
175 p, starred=p.project_id in logged_in_starred_pids)
176 for p in starred_projects],
177 'starred_users': starred_users,
178 'starred_users_json': starred_users_json,
179 'is_user_starred': ezt.boolean(is_user_starred),
180 'viewing_user_page': ezt.boolean(True),
181 'last_visit_str': last_visit_str,
182 'last_bounce_str': last_bounce_str,
183 'vacation_message': viewed_user.vacation_message,
184 'can_ban': ezt.boolean(can_ban),
185 'ban_token': ban_token,
186 'ban_spammer_token': ban_spammer_token,
187 'user_groups': user_group_views,
188 'linked_parent': linked_parent,
189 'linked_children': linked_children,
190 'incoming_invite_users': incoming_invite_users,
191 'outgoing_invite_users': outgoing_invite_users,
192 'possible_parent_accounts': possible_parent_accounts,
193 'can_edit_invites': ezt.boolean(can_edit_invites),
194 'offer_unlink': ezt.boolean(offer_unlink),
195 'can_delete_user': ezt.boolean(can_delete_user),
196 }
197
198 viewed_user_prefs = None
199 if mr.perms.HasPerm(permissions.EDIT_OTHER_USERS, None, None):
200 with work_env.WorkEnv(mr, self.services) as we:
201 viewed_user_prefs = we.GetUserPrefs(mr.viewed_user_auth.user_id)
202
203 user_settings = (
204 framework_helpers.UserSettings.GatherUnifiedSettingsPageData(
205 mr.auth.user_id, mr.viewed_user_auth.user_view, viewed_user,
206 viewed_user_prefs))
207 page_data.update(user_settings)
208
209 return page_data
210
211 def _IsUserStarred(self, cnxn, logged_in_user_id, viewed_user_id):
212 """Return whether the logged in user starred the viewed user."""
213 if logged_in_user_id:
214 return self.services.user_star.IsItemStarredBy(
215 cnxn, viewed_user_id, logged_in_user_id)
216 return False
217
218 def ProcessFormData(self, mr, post_data):
219 """Process the posted form."""
220 has_admin_perm = mr.perms.HasPerm(permissions.EDIT_OTHER_USERS, None, None)
221 with work_env.WorkEnv(mr, self.services) as we:
222 framework_helpers.UserSettings.ProcessSettingsForm(
223 we, post_data, mr.viewed_user_auth.user_pb, admin=has_admin_perm)
224
225 # TODO(jrobbins): Check all calls to FormatAbsoluteURL for include_project.
226 return framework_helpers.FormatAbsoluteURL(
227 mr, mr.viewed_user_auth.user_view.profile_url, include_project=False,
228 saved=1, ts=int(time.time()))
229
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100230 def GetUserProfilePage(self, **kwargs):
231 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200232
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100233 def PostUserProfilePage(self, **kwargs):
234 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200235
Copybara854996b2021-09-07 19:36:02 +0000236
237def _ComputePossibleParentAccounts(
238 we, user_view, linked_parent, linked_children):
239 """Return a list of email addresses of possible parent accounts."""
240 if not user_view:
241 return [] # Anon user cannot link to any account.
242 if linked_parent or linked_children:
243 return [] # If account is already linked in any way, don't offer.
244 possible_domains = settings.linkable_domains.get(user_view.domain, [])
245 possible_emails = ['%s@%s' % (user_view.username, domain)
246 for domain in possible_domains]
247 found_users, _ = we.ListReferencedUsers(possible_emails)
248 found_emails = [user.email for user in found_users]
249 return found_emails
250
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100251
Copybara854996b2021-09-07 19:36:02 +0000252class BanUser(servlet.Servlet):
253 """Bans or un-bans a user."""
254
255 def ProcessFormData(self, mr, post_data):
256 """Process the posted form."""
257 if not permissions.CanBan(mr, self.services):
258 raise permissions.PermissionException(
259 "You do not have permission to ban users.")
260
261 framework_helpers.UserSettings.ProcessBanForm(
262 mr.cnxn, self.services.user, post_data, mr.viewed_user_auth.user_id,
263 mr.viewed_user_auth.user_pb)
264
265 # TODO(jrobbins): Check all calls to FormatAbsoluteURL for include_project.
266 return framework_helpers.FormatAbsoluteURL(
267 mr, mr.viewed_user_auth.user_view.profile_url, include_project=False,
268 saved=1, ts=int(time.time()))
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200269
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100270 def PostBanUserPage(self, **kwargs):
271 return self.handler(**kwargs)