OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is govered 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 """JSON feed for issue autocomplete options.""" |
| 7 |
| 8 import logging |
| 9 from third_party import ezt |
| 10 |
| 11 from framework import framework_helpers |
| 12 from framework import framework_views |
| 13 from framework import jsonfeed |
| 14 from framework import monorailrequest |
| 15 from framework import permissions |
| 16 from project import project_helpers |
| 17 from tracker import tracker_helpers |
| 18 from tracker import tracker_views |
| 19 |
| 20 |
| 21 # Here are some restriction labels to help people do the most common things |
| 22 # that they might want to do with restrictions. |
| 23 _FREQUENT_ISSUE_RESTRICTIONS = [ |
| 24 (permissions.VIEW, permissions.EDIT_ISSUE, |
| 25 'Only users who can edit the issue may access it'), |
| 26 (permissions.ADD_ISSUE_COMMENT, permissions.EDIT_ISSUE, |
| 27 'Only users who can edit the issue may add comments'), |
| 28 ] |
| 29 |
| 30 |
| 31 # These issue restrictions should be offered as examples whenever the project |
| 32 # does not have any custom permissions in use already. |
| 33 _EXAMPLE_ISSUE_RESTRICTIONS = [ |
| 34 (permissions.VIEW, 'CoreTeam', |
| 35 'Custom permission CoreTeam is needed to access'), |
| 36 ] |
| 37 |
| 38 |
| 39 class IssueOptionsJSON(jsonfeed.JsonFeed): |
| 40 """JSON data describing all issue statuses, labels, and members.""" |
| 41 |
| 42 def HandleRequest(self, mr): |
| 43 """Provide the UI with info used in auto-completion. |
| 44 |
| 45 Args: |
| 46 mr: common information parsed from the HTTP request. |
| 47 |
| 48 Returns: |
| 49 Results dictionary in JSON format |
| 50 """ |
| 51 # Issue options data can be cached separately in each user's browser. When |
| 52 # the project changes, a new cached_content_timestamp is set and it will |
| 53 # cause new requests to use a new URL. |
| 54 self.SetCacheHeaders(self.response) |
| 55 |
| 56 member_data = project_helpers.BuildProjectMembers( |
| 57 mr.cnxn, mr.project, self.services.user) |
| 58 owner_views = member_data['owners'] |
| 59 committer_views = member_data['committers'] |
| 60 contributor_views = member_data['contributors'] |
| 61 |
| 62 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id) |
| 63 |
| 64 open_statuses = [] |
| 65 closed_statuses = [] |
| 66 for wks in config.well_known_statuses: |
| 67 if not wks.deprecated: |
| 68 item = dict(name=wks.status, doc=wks.status_docstring) |
| 69 if wks.means_open: |
| 70 open_statuses.append(item) |
| 71 else: |
| 72 closed_statuses.append(item) |
| 73 |
| 74 # TODO(jrobbins): restrictions on component definitions? |
| 75 components = [{'name': cd.path, 'doc': cd.docstring} |
| 76 for cd in config.component_defs if not cd.deprecated] |
| 77 |
| 78 labels = [] |
| 79 field_names = [ |
| 80 fd.field_name for fd in config.field_defs if not fd.is_deleted] |
| 81 non_masked_labels = tracker_helpers.LabelsNotMaskedByFields( |
| 82 config, field_names) |
| 83 for wkl in non_masked_labels: |
| 84 if not wkl.commented: |
| 85 item = dict(name=wkl.name, doc=wkl.docstring) |
| 86 labels.append(item) |
| 87 |
| 88 # TODO(jrobbins): omit fields that they don't have permission to view. |
| 89 field_def_views = [ |
| 90 tracker_views.FieldDefView(fd, config) |
| 91 for fd in config.field_defs |
| 92 if not fd.is_deleted] |
| 93 fields = [ |
| 94 dict(field_name=fdv.field_name, field_type=fdv.field_type, |
| 95 field_id=fdv.field_id, needs_perm=fdv.needs_perm, |
| 96 is_required=fdv.is_required, is_multivalued=fdv.is_multivalued, |
| 97 choices=[dict(name=c.name, doc=c.docstring) for c in fdv.choices], |
| 98 docstring=fdv.docstring) |
| 99 for fdv in field_def_views] |
| 100 |
| 101 frequent_restrictions = _FREQUENT_ISSUE_RESTRICTIONS[:] |
| 102 custom_permissions = permissions.GetCustomPermissions(mr.project) |
| 103 if not custom_permissions: |
| 104 frequent_restrictions.extend( |
| 105 _EXAMPLE_ISSUE_RESTRICTIONS) |
| 106 |
| 107 labels.extend(_BuildRestrictionChoices( |
| 108 mr.project, frequent_restrictions, |
| 109 permissions.STANDARD_ISSUE_PERMISSIONS)) |
| 110 |
| 111 group_ids = self.services.usergroup.DetermineWhichUserIDsAreGroups( |
| 112 mr.cnxn, [mem.user_id for mem in member_data['all_members']]) |
| 113 logging.info('group_ids is %r', group_ids) |
| 114 |
| 115 # TODO(jrobbins): Normally, users will be allowed view the members |
| 116 # of any user group if the project From: email address is listed |
| 117 # as a group member, as well as any group that they are personally |
| 118 # members of. |
| 119 member_ids, owner_ids = self.services.usergroup.LookupVisibleMembers( |
| 120 mr.cnxn, group_ids, mr.perms, mr.auth.effective_ids, self.services) |
| 121 indirect_ids = set() |
| 122 for gid in group_ids: |
| 123 indirect_ids.update(member_ids.get(gid, [])) |
| 124 indirect_ids.update(owner_ids.get(gid, [])) |
| 125 indirect_user_ids = list(indirect_ids) |
| 126 indirect_member_views = framework_views.MakeAllUserViews( |
| 127 mr.cnxn, self.services.user, indirect_user_ids).values() |
| 128 |
| 129 visible_member_views = _FilterMemberData( |
| 130 mr, owner_views, committer_views, contributor_views, |
| 131 indirect_member_views) |
| 132 # Filter out servbice accounts |
| 133 visible_member_views = [m for m in visible_member_views |
| 134 if not framework_helpers.IsServiceAccount(m.email)] |
| 135 visible_member_email_list = list({ |
| 136 uv.email for uv in visible_member_views}) |
| 137 user_indexes = {email: idx |
| 138 for idx, email in enumerate(visible_member_email_list)} |
| 139 visible_members_dict = {} |
| 140 for uv in visible_member_views: |
| 141 visible_members_dict[uv.email] = uv.user_id |
| 142 group_ids = self.services.usergroup.DetermineWhichUserIDsAreGroups( |
| 143 mr.cnxn, visible_members_dict.values()) |
| 144 |
| 145 for field_dict in fields: |
| 146 needed_perm = field_dict['needs_perm'] |
| 147 if needed_perm: |
| 148 qualified_user_indexes = [] |
| 149 for uv in visible_member_views: |
| 150 # TODO(jrobbins): Similar code occurs in field_helpers.py. |
| 151 user = self.services.user.GetUser(mr.cnxn, uv.user_id) |
| 152 auth = monorailrequest.AuthData.FromUserID( |
| 153 mr.cnxn, uv.user_id, self.services) |
| 154 user_perms = permissions.GetPermissions( |
| 155 user, auth.effective_ids, mr.project) |
| 156 has_perm = user_perms.CanUsePerm( |
| 157 needed_perm, auth.effective_ids, mr.project, []) |
| 158 if has_perm: |
| 159 qualified_user_indexes.append(user_indexes[uv.email]) |
| 160 |
| 161 field_dict['user_indexes'] = sorted(set(qualified_user_indexes)) |
| 162 |
| 163 excl_prefixes = [prefix.lower() for prefix in |
| 164 config.exclusive_label_prefixes] |
| 165 members_def_list = [dict(name=email, doc='') |
| 166 for email in visible_member_email_list] |
| 167 members_def_list = sorted( |
| 168 members_def_list, key=lambda md: md['name']) |
| 169 for md in members_def_list: |
| 170 md_id = visible_members_dict[md['name']] |
| 171 if md_id in group_ids: |
| 172 md['is_group'] = True |
| 173 |
| 174 return { |
| 175 'open': open_statuses, |
| 176 'closed': closed_statuses, |
| 177 'statuses_offer_merge': config.statuses_offer_merge, |
| 178 'components': components, |
| 179 'labels': labels, |
| 180 'fields': fields, |
| 181 'excl_prefixes': excl_prefixes, |
| 182 'strict': ezt.boolean(config.restrict_to_known), |
| 183 'members': members_def_list, |
| 184 'custom_permissions': custom_permissions, |
| 185 } |
| 186 |
| 187 |
| 188 def _FilterMemberData( |
| 189 mr, owner_views, committer_views, contributor_views, |
| 190 indirect_member_views): |
| 191 """Return a filtered list of members that the user can view. |
| 192 |
| 193 In most projects, everyone can view the entire member list. But, |
| 194 some projects are configured to only allow project owners to see |
| 195 all members. In those projects, committers and contributors do not |
| 196 see any contributors. Regardless of how the project is configured |
| 197 or the role that the user plays in the current project, we include |
| 198 any indirect members through user groups that the user has access |
| 199 to view. |
| 200 |
| 201 Args: |
| 202 mr: Commonly used info parsed from the HTTP request. |
| 203 owner_views: list of UserViews for project owners. |
| 204 committer_views: list of UserViews for project committers. |
| 205 contributor_views: list of UserViews for project contributors. |
| 206 indirect_member_views: list of UserViews for users who have |
| 207 an indirect role in the project via a user group, and that the |
| 208 logged in user is allowed to see. |
| 209 |
| 210 Returns: |
| 211 A list of owners, committer and visible indirect members if the user is not |
| 212 signed in. If the project is set to display contributors to non-owners or |
| 213 the signed in user has necessary permissions then additionally a list of |
| 214 contributors. |
| 215 """ |
| 216 visible_members = [] |
| 217 |
| 218 # Everyone can view owners and committers |
| 219 visible_members.extend(owner_views) |
| 220 visible_members.extend(committer_views) |
| 221 |
| 222 # The list of indirect members is already limited to ones that the user |
| 223 # is allowed to see according to user group settings. |
| 224 visible_members.extend(indirect_member_views) |
| 225 |
| 226 # If the user is allowed to view the list of contributors, add those too. |
| 227 if permissions.CanViewContributorList(mr): |
| 228 visible_members.extend(contributor_views) |
| 229 |
| 230 return visible_members |
| 231 |
| 232 |
| 233 def _BuildRestrictionChoices(project, freq_restrictions, actions): |
| 234 """Return a list of autocompletion choices for restriction labels. |
| 235 |
| 236 Args: |
| 237 project: Project PB for the current project. |
| 238 freq_restrictions: list of (action, perm, doc) tuples for restrictions |
| 239 that are frequently used. |
| 240 actions: list of strings for actions that are relevant to the current |
| 241 artifact. |
| 242 |
| 243 Returns: |
| 244 A list of dictionaries [{'name': 'perm name', 'doc': 'docstring'}, ...] |
| 245 suitable for use in a JSON feed to our JS autocompletion functions. |
| 246 """ |
| 247 custom_permissions = permissions.GetCustomPermissions(project) |
| 248 choices = [] |
| 249 |
| 250 for action, perm, doc in freq_restrictions: |
| 251 choices.append({ |
| 252 'name': 'Restrict-%s-%s' % (action, perm), |
| 253 'doc': doc, |
| 254 }) |
| 255 |
| 256 for action in actions: |
| 257 for perm in custom_permissions: |
| 258 choices.append({ |
| 259 'name': 'Restrict-%s-%s' % (action, perm), |
| 260 'doc': 'Permission %s needed to use %s' % (perm, action), |
| 261 }) |
| 262 |
| 263 return choices |
OLD | NEW |