Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | # 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").""" |
| 7 | from __future__ import print_function |
| 8 | from __future__ import division |
| 9 | from __future__ import absolute_import |
| 10 | |
| 11 | import logging |
| 12 | import time |
| 13 | import json |
| 14 | |
| 15 | import ezt |
| 16 | |
| 17 | import settings |
| 18 | from businesslogic import work_env |
| 19 | from framework import framework_helpers |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 20 | from framework import flaskservlet |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 21 | from framework import framework_views |
| 22 | from framework import permissions |
| 23 | from framework import servlet |
| 24 | from framework import timestr |
| 25 | from framework import xsrf |
| 26 | from project import project_views |
| 27 | from sitewide import sitewide_helpers |
| 28 | |
| 29 | |
| 30 | class 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ínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 150 | form_token_path = mr.request_path + 'ban.do' |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 151 | ban_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path) |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 152 | form_token_path = mr.request_path + 'banSpammer.do' |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 153 | 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ínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 234 | # def GetUserProfilePage(self, **kwargs): |
| 235 | # return self.handler(**kwargs) |
| 236 | |
| 237 | # def PostUserProfilePage(self, **kwargs): |
| 238 | # return self.handler(**kwargs) |
| 239 | |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 240 | |
| 241 | def _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 | |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 255 | class 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ínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 272 | |
| 273 | # def PostBanUserPage(self, **kwargs): |
| 274 | # return self.handler(**kwargs) |