Index: appengine/monorail/framework/framework_views.py |
diff --git a/appengine/monorail/framework/framework_views.py b/appengine/monorail/framework/framework_views.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2b9453c44aee977f4ce19ad51b69e9af344998a3 |
--- /dev/null |
+++ b/appengine/monorail/framework/framework_views.py |
@@ -0,0 +1,214 @@ |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is govered by a BSD-style |
+# license that can be found in the LICENSE file or at |
+# https://developers.google.com/open-source/licenses/bsd |
+ |
+"""View classes to make it easy to display framework objects in EZT.""" |
+ |
+from third_party import ezt |
+ |
+from framework import framework_bizobj |
+from framework import framework_constants |
+from framework import permissions |
+from framework import template_helpers |
+from services import client_config_svc |
+import settings |
+ |
+ |
+_LABEL_DISPLAY_CHARS = 30 |
+_LABEL_PART_DISPLAY_CHARS = 15 |
+ |
+ |
+class LabelView(object): |
+ """Wrapper class that makes it easier to display a label via EZT.""" |
+ |
+ def __init__(self, label, config): |
+ """Make several values related to this label available as attrs. |
+ |
+ Args: |
+ label: artifact label string. E.g., 'Priority-High' or 'Frontend'. |
+ config: PB with a well_known_labels list, or None. |
+ """ |
+ self.name = label |
+ self.tooltip = label |
+ self.is_restrict = ezt.boolean(permissions.IsRestrictLabel(label)) |
+ |
+ self.docstring = '' |
+ if config: |
+ for wkl in config.well_known_labels: |
+ if label.lower() == wkl.label.lower(): |
+ self.docstring = wkl.label_docstring |
+ |
+ if '-' in label: |
+ self.prefix, self.value = label.split('-', 1) |
+ else: |
+ self.prefix, self.value = '', label |
+ |
+ |
+class StatusView(object): |
+ """Wrapper class that makes it easier to display a status via EZT.""" |
+ |
+ def __init__(self, status, config): |
+ """Make several values related to this status available as attrs. |
+ |
+ Args: |
+ status: artifact status string. E.g., 'New' or 'Accepted'. |
+ config: PB with a well_known_statuses list, or None. |
+ """ |
+ |
+ self.name = status |
+ self.tooltip = status |
+ |
+ self.docstring = '' |
+ self.means_open = ezt.boolean(True) |
+ if config: |
+ for wks in config.well_known_statuses: |
+ if status.lower() == wks.status.lower(): |
+ self.docstring = wks.status_docstring |
+ self.means_open = ezt.boolean(wks.means_open) |
+ |
+ |
+class UserView(object): |
+ """Wrapper class to easily display basic user information in a template.""" |
+ |
+ def __init__(self, user_id, email, obscure_email): |
+ email = email or '' |
+ self.user_id = user_id |
+ self.email = email |
+ self.profile_url = '/u/%s/' % user_id |
+ self.obscure_email = obscure_email |
+ self.banned = '' |
+ |
+ (self.username, self.domain, |
+ self.obscured_username) = ParseAndObscureAddress(email) |
+ # No need to obfuscate or reveal client email. |
+ # Instead display a human-readable username. |
+ if not self.email: |
+ self.display_name = 'a deleted user' |
+ self.obscure_email = '' |
+ self.profile_url = '' |
+ elif self.email in client_config_svc.GetServiceAccountMap(): |
+ self.display_name = client_config_svc.GetServiceAccountMap()[self.email] |
+ elif not self.obscure_email: |
+ self.display_name = email |
+ else: |
+ self.display_name = '%s...@%s' % (self.obscured_username, self.domain) |
+ |
+ def RevealEmail(self): |
+ if not self.email: |
+ return |
+ if self.email not in client_config_svc.GetServiceAccountMap(): |
+ self.obscure_email = False |
+ self.display_name = self.email |
+ self.profile_url = '/u/%s/' % self.email |
+ |
+ |
+def MakeAllUserViews(cnxn, user_service, *list_of_user_id_lists): |
+ """Make a dict {user_id: user_view, ...} for all user IDs given.""" |
+ distinct_user_ids = set() |
+ distinct_user_ids.update(*list_of_user_id_lists) |
+ user_dict = user_service.GetUsersByIDs(cnxn, distinct_user_ids) |
+ return {user_id: UserView(user_id, user_pb.email, user_pb.obscure_email) |
+ for user_id, user_pb in user_dict.iteritems()} |
+ |
+ |
+def MakeUserView(cnxn, user_service, user_id): |
+ """Make a UserView for the given user ID.""" |
+ user = user_service.GetUser(cnxn, user_id) |
+ return UserView(user_id, user.email, user.obscure_email) |
+ |
+ |
+def ParseAndObscureAddress(email): |
+ """Break the given email into username and domain, and obscure. |
+ |
+ Args: |
+ email: string email address to process |
+ |
+ Returns: |
+ A 3-tuple (username, domain, obscured_username). |
+ The obscured_username is trucated the same way that Google Groups does it. |
+ """ |
+ if '@' in email: |
+ username, user_domain = email.split('@', 1) |
+ else: # don't fail if User table has unexpected email address format. |
+ username, user_domain = email, '' |
+ |
+ base_username = username.split('+')[0] |
+ cutoff_point = min(8, max(1, len(base_username) - 3)) |
+ obscured_username = base_username[:cutoff_point] |
+ |
+ return username, user_domain, obscured_username |
+ |
+ |
+def _ShouldRevealEmail(auth, project, viewed_email): |
+ """Decide whether to publish a user's email address. |
+ |
+ Args: |
+ auth: The AuthData of the user viewing the email addresses. |
+ project: The project to which the viewed users belong. |
+ viewed_email: The email of the viewed user. |
+ |
+ Returns: |
+ True if email addresses should be published to the logged-in user. |
+ """ |
+ # Case 1: Anon users don't see anything revealed. |
+ if auth.user_pb is None: |
+ return False |
+ |
+ # Case 2: site admins always see unobscured email addresses. |
+ if auth.user_pb.is_site_admin: |
+ return True |
+ |
+ # Case 3: Domain users in same-org-only projects always see unobscured addrs. |
+ # TODO(jrobbins): re-implement same_org |
+ |
+ # Case 4: Project members see the unobscured email of everyone in a project. |
+ if project and framework_bizobj.UserIsInProject(project, auth.effective_ids): |
+ return True |
+ |
+ # Case 5: Emails that end in priviledged user domains see unobscured email |
+ # addresses. |
+ if framework_bizobj.IsPriviledgedDomainUser(auth.user_pb.email): |
+ return True |
+ |
+ # Case 6: Do not obscure your own email. |
+ if viewed_email and auth.user_pb.email == viewed_email: |
+ return True |
+ |
+ return False |
+ |
+ |
+def RevealAllEmailsToMembers(mr, users_by_id): |
+ """Allow project members to see unobscured email addresses in that project. |
+ |
+ Non project member addresses will be obscured. |
+ Site admins can see all email addresses unobscured. |
+ |
+ Args: |
+ mr: common info parsed from the user's request. |
+ users_by_id: dictionary of UserView's that will be displayed. |
+ |
+ Returns: |
+ Nothing, but the UserViews in users_by_id may be modified to |
+ publish email address. |
+ """ |
+ for user_view in users_by_id.itervalues(): |
+ if _ShouldRevealEmail(mr.auth, mr.project, user_view.email): |
+ user_view.RevealEmail() |
+ |
+ |
+def RevealAllEmails(users_by_id): |
+ """Allow anyone to see unobscured email addresses of project members. |
+ |
+ The modified view objects should only be used to generate views for other |
+ project members. |
+ |
+ Args: |
+ users_by_id: dictionary of UserViews that will be displayed. |
+ |
+ Returns: |
+ Nothing, but the UserViews in users_by_id may be modified to |
+ publish email address. |
+ """ |
+ for user_view in users_by_id.itervalues(): |
+ user_view.RevealEmail() |