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