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 | """View classes to make it easy to display framework objects in EZT.""" |
| 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 | |
| 14 | import ezt |
| 15 | |
| 16 | from framework import framework_bizobj |
| 17 | from framework import framework_constants |
| 18 | from framework import framework_helpers |
| 19 | from framework import permissions |
| 20 | from framework import template_helpers |
| 21 | from framework import timestr |
| 22 | from proto import user_pb2 |
| 23 | from services import client_config_svc |
| 24 | import settings |
| 25 | |
| 26 | |
| 27 | _LABEL_DISPLAY_CHARS = 30 |
| 28 | _LABEL_PART_DISPLAY_CHARS = 15 |
| 29 | |
| 30 | |
| 31 | class LabelView(object): |
| 32 | """Wrapper class that makes it easier to display a label via EZT.""" |
| 33 | |
| 34 | def __init__(self, label, config): |
| 35 | """Make several values related to this label available as attrs. |
| 36 | |
| 37 | Args: |
| 38 | label: artifact label string. E.g., 'Priority-High' or 'Frontend'. |
| 39 | config: PB with a well_known_labels list, or None. |
| 40 | """ |
| 41 | self.name = label |
| 42 | self.is_restrict = ezt.boolean(permissions.IsRestrictLabel(label)) |
| 43 | |
| 44 | self.docstring = '' |
| 45 | if config: |
| 46 | for wkl in config.well_known_labels: |
| 47 | if label.lower() == wkl.label.lower(): |
| 48 | self.docstring = wkl.label_docstring |
| 49 | |
| 50 | if '-' in label: |
| 51 | self.prefix, self.value = label.split('-', 1) |
| 52 | else: |
| 53 | self.prefix, self.value = '', label |
| 54 | |
| 55 | |
| 56 | class StatusView(object): |
| 57 | """Wrapper class that makes it easier to display a status via EZT.""" |
| 58 | |
| 59 | def __init__(self, status, config): |
| 60 | """Make several values related to this status available as attrs. |
| 61 | |
| 62 | Args: |
| 63 | status: artifact status string. E.g., 'New' or 'Accepted'. |
| 64 | config: PB with a well_known_statuses list, or None. |
| 65 | """ |
| 66 | |
| 67 | self.name = status |
| 68 | |
| 69 | self.docstring = '' |
| 70 | self.means_open = ezt.boolean(True) |
| 71 | if config: |
| 72 | for wks in config.well_known_statuses: |
| 73 | if status.lower() == wks.status.lower(): |
| 74 | self.docstring = wks.status_docstring |
| 75 | self.means_open = ezt.boolean(wks.means_open) |
| 76 | |
| 77 | |
| 78 | class UserView(object): |
| 79 | """Wrapper class to easily display basic user information in a template.""" |
| 80 | |
| 81 | def __init__(self, user, is_group=False): |
| 82 | self.user = user |
| 83 | self.is_group = is_group |
| 84 | email = user.email or '' |
| 85 | self.user_id = user.user_id |
| 86 | self.email = email |
| 87 | if user.obscure_email: |
| 88 | self.profile_url = '/u/%s/' % user.user_id |
| 89 | else: |
| 90 | self.profile_url = '/u/%s/' % email |
| 91 | self.obscure_email = user.obscure_email |
| 92 | self.banned = '' |
| 93 | |
| 94 | (self.username, self.domain, self.obscured_username, |
| 95 | obscured_email) = framework_bizobj.ParseAndObscureAddress(email) |
| 96 | # No need to obfuscate or reveal client email. |
| 97 | # Instead display a human-readable username. |
| 98 | if self.user_id == framework_constants.DELETED_USER_ID: |
| 99 | self.display_name = framework_constants.DELETED_USER_NAME |
| 100 | self.obscure_email = '' |
| 101 | self.profile_url = '' |
| 102 | elif self.email in client_config_svc.GetServiceAccountMap(): |
| 103 | self.display_name = client_config_svc.GetServiceAccountMap()[self.email] |
| 104 | elif not self.obscure_email: |
| 105 | self.display_name = email |
| 106 | else: |
| 107 | self.display_name = obscured_email |
| 108 | |
| 109 | self.avail_message, self.avail_state = ( |
| 110 | framework_helpers.GetUserAvailability(user, is_group)) |
| 111 | self.avail_message_short = template_helpers.FitUnsafeText( |
| 112 | self.avail_message, 35) |
| 113 | |
| 114 | def RevealEmail(self): |
| 115 | if not self.email: |
| 116 | return |
| 117 | if self.email not in client_config_svc.GetServiceAccountMap(): |
| 118 | self.obscure_email = False |
| 119 | self.display_name = self.email |
| 120 | self.profile_url = '/u/%s/' % self.email |
| 121 | |
| 122 | |
| 123 | def MakeAllUserViews( |
| 124 | cnxn, user_service, *list_of_user_id_lists, **kw): |
| 125 | """Make a dict {user_id: user_view, ...} for all user IDs given.""" |
| 126 | distinct_user_ids = set() |
| 127 | distinct_user_ids.update(*list_of_user_id_lists) |
| 128 | if None in distinct_user_ids: |
| 129 | distinct_user_ids.remove(None) |
| 130 | group_ids = kw.get('group_ids', []) |
| 131 | user_dict = user_service.GetUsersByIDs(cnxn, distinct_user_ids) |
| 132 | return {user_id: UserView(user_pb, is_group=user_id in group_ids) |
| 133 | for user_id, user_pb in user_dict.items()} |
| 134 | |
| 135 | |
| 136 | def MakeUserView(cnxn, user_service, user_id): |
| 137 | """Make a UserView for the given user ID.""" |
| 138 | user = user_service.GetUser(cnxn, user_id) |
| 139 | return UserView(user) |
| 140 | |
| 141 | |
| 142 | def StuffUserView(user_id, email, obscure_email): |
| 143 | """Construct a UserView with the given parameters for testing.""" |
| 144 | user = user_pb2.MakeUser(user_id, email=email, obscure_email=obscure_email) |
| 145 | return UserView(user) |
| 146 | |
| 147 | |
| 148 | # TODO(https://crbug.com/monorail/8192): Remove optional project. |
| 149 | def RevealAllEmailsToMembers(cnxn, services, auth, users_by_id, project=None): |
| 150 | # type: (MonorailConnection, Services, AuthData, Collection[user_pb2.User], |
| 151 | # Optional[project_pb2.Project] -> None) |
| 152 | """Reveal emails based on the authenticated user. |
| 153 | |
| 154 | The actual behavior can be determined by looking into |
| 155 | framework_bizobj.ShouldRevealEmail. Look at https://crbug.com/monorail/8030 |
| 156 | for context. |
| 157 | This method should be deleted when endpoints and ezt pages are deprecated. |
| 158 | |
| 159 | Args: |
| 160 | cnxn: MonorailConnection to the database. |
| 161 | services: Services object for connections to backend services. |
| 162 | auth: AuthData object that identifies the logged in user. |
| 163 | users_by_id: dictionary of UserView's that might be displayed. |
| 164 | project: Optional Project PB for the current project. |
| 165 | |
| 166 | Returns: |
| 167 | Nothing, but the UserViews in users_by_id may be modified to |
| 168 | publish email address. |
| 169 | """ |
| 170 | if project: |
| 171 | for user_view in users_by_id.values(): |
| 172 | if framework_bizobj.DeprecatedShouldRevealEmail(auth, project, |
| 173 | user_view.email): |
| 174 | user_view.RevealEmail() |
| 175 | else: |
| 176 | viewable_users = framework_bizobj.FilterViewableEmails( |
| 177 | cnxn, services, auth, users_by_id.values()) |
| 178 | for user_view in viewable_users: |
| 179 | user_view.RevealEmail() |
| 180 | |
| 181 | |
| 182 | def RevealAllEmails(users_by_id): |
| 183 | """Allow anyone to see unobscured email addresses of project members. |
| 184 | |
| 185 | The modified view objects should only be used to generate views for other |
| 186 | project members. |
| 187 | |
| 188 | Args: |
| 189 | users_by_id: dictionary of UserViews that will be displayed. |
| 190 | |
| 191 | Returns: |
| 192 | Nothing, but the UserViews in users_by_id may be modified to |
| 193 | publish email address. |
| 194 | """ |
| 195 | for user_view in users_by_id.values(): |
| 196 | user_view.RevealEmail() |
| 197 | |
| 198 | |
| 199 | def GetViewedUserDisplayName(mr): |
| 200 | """Get display name of the viewed user given the logged-in user.""" |
| 201 | # Do not obscure email if current user is a site admin. Do not obscure |
| 202 | # email if current user is viewing their own profile. For all other |
| 203 | # cases do whatever obscure_email setting for the user is. |
| 204 | viewing_self = mr.auth.user_id == mr.viewed_user_auth.user_id |
| 205 | email_obscured = (not(mr.auth.user_pb.is_site_admin or viewing_self) |
| 206 | and mr.viewed_user_auth.user_view.obscure_email) |
| 207 | if email_obscured: |
| 208 | (_username, _domain, _obscured_username, |
| 209 | obscured_email) = framework_bizobj.ParseAndObscureAddress( |
| 210 | mr.viewed_user_auth.email) |
| 211 | viewed_user_display_name = obscured_email |
| 212 | else: |
| 213 | viewed_user_display_name = mr.viewed_user_auth.email |
| 214 | |
| 215 | return viewed_user_display_name |