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 """Servlet that searches for issues that the specified user cannot view. |
| 7 |
| 8 The GET request to a backend has query string parameters for the |
| 9 shard_id, a user_id, and list of project IDs. It returns a |
| 10 JSON-formatted dict with issue_ids that that user is not allowed to |
| 11 view. As a side-effect, this servlet updates multiple entries |
| 12 in memcache, including each "nonviewable:USER_ID;PROJECT_ID;SHARD_ID". |
| 13 """ |
| 14 |
| 15 import logging |
| 16 |
| 17 from google.appengine.api import memcache |
| 18 |
| 19 from framework import framework_constants |
| 20 from framework import framework_helpers |
| 21 from framework import jsonfeed |
| 22 from framework import permissions |
| 23 from framework import sql |
| 24 |
| 25 |
| 26 RESTRICT_VIEW_PATTERN = 'restrict-view-%' |
| 27 |
| 28 # We cache the set of IIDs that a given user cannot view, and we invalidate |
| 29 # that set when the issues are changed via Monorail. Also, we limit the live |
| 30 # those cache entries so that changes in a user's (direct or indirect) roles |
| 31 # in a project will take effect. |
| 32 NONVIEWABLE_MEMCACHE_EXPIRATION = 15 * framework_constants.SECS_PER_MINUTE |
| 33 |
| 34 |
| 35 class BackendNonviewable(jsonfeed.InternalTask): |
| 36 """JSON servlet for getting issue IDs that the specified user cannot view.""" |
| 37 |
| 38 CHECK_SAME_APP = True |
| 39 |
| 40 def HandleRequest(self, mr): |
| 41 """Get all the user IDs that the specified user cannot view. |
| 42 |
| 43 Args: |
| 44 mr: common information parsed from the HTTP request. |
| 45 |
| 46 Returns: |
| 47 Results dictionary {project_id: [issue_id]} in JSON format. |
| 48 """ |
| 49 if mr.shard_id is None: |
| 50 return {'message': 'Cannot proceed without a valid shard_id.'} |
| 51 user_id = mr.specified_logged_in_user_id |
| 52 user = self.services.user.GetUser(mr.cnxn, user_id) |
| 53 effective_ids = self.services.usergroup.LookupMemberships(mr.cnxn, user_id) |
| 54 if user_id: |
| 55 effective_ids.add(user_id) |
| 56 project_id = mr.specified_project_id |
| 57 project = self.services.project.GetProject(mr.cnxn, project_id) |
| 58 |
| 59 perms = permissions.GetPermissions(user, effective_ids, project) |
| 60 |
| 61 nonviewable_iids = self.GetNonviewableIIDs( |
| 62 mr.cnxn, user, effective_ids, project, perms, mr.shard_id) |
| 63 |
| 64 cached_ts = mr.invalidation_timestep |
| 65 if mr.specified_project_id: |
| 66 memcache.set( |
| 67 'nonviewable:%d;%d;%d' % (project_id, user_id, mr.shard_id), |
| 68 (nonviewable_iids, cached_ts), |
| 69 time=NONVIEWABLE_MEMCACHE_EXPIRATION) |
| 70 else: |
| 71 memcache.set( |
| 72 'nonviewable:all;%d;%d' % (user_id, mr.shard_id), |
| 73 (nonviewable_iids, cached_ts), |
| 74 time=NONVIEWABLE_MEMCACHE_EXPIRATION) |
| 75 |
| 76 logging.info('set nonviewable:%s;%d;%d to %r', project_id, user_id, |
| 77 mr.shard_id, nonviewable_iids) |
| 78 |
| 79 return { |
| 80 'nonviewable': nonviewable_iids, |
| 81 |
| 82 # These are not used in the frontend, but useful for debugging. |
| 83 'project_id': project_id, |
| 84 'user_id': user_id, |
| 85 'shard_id': mr.shard_id, |
| 86 } |
| 87 |
| 88 def GetNonviewableIIDs( |
| 89 self, cnxn, user, effective_ids, project, perms, shard_id): |
| 90 """Return a list of IIDs that the user cannot view in the project shard.""" |
| 91 # Project owners and site admins can see all issues. |
| 92 if not perms.consider_restrictions: |
| 93 return [] |
| 94 |
| 95 # There are two main parts to the computation that we do in parallel: |
| 96 # getting at-risk IIDs and getting OK-iids. |
| 97 cnxn_2 = sql.MonorailConnection() |
| 98 at_risk_iids_promise = framework_helpers.Promise( |
| 99 self.GetAtRiskIIDs, cnxn_2, user, effective_ids, project, perms, shard_id) |
| 100 ok_iids = self.GetViewableIIDs( |
| 101 cnxn, effective_ids, project.project_id, shard_id) |
| 102 at_risk_iids = at_risk_iids_promise.WaitAndGetValue() |
| 103 |
| 104 # The set of non-viewable issues is the at-risk ones minus the ones where |
| 105 # the user is the reporter, owner, CC'd, or granted "View" permission. |
| 106 nonviewable_iids = set(at_risk_iids).difference(ok_iids) |
| 107 |
| 108 return list(nonviewable_iids) |
| 109 |
| 110 def GetAtRiskIIDs( |
| 111 self, cnxn, user, effective_ids, project, perms, shard_id): |
| 112 """Return IIDs of restricted issues that user might not be able to view.""" |
| 113 at_risk_label_ids = self.GetPersonalAtRiskLabelIDs( |
| 114 cnxn, user, effective_ids, project, perms) |
| 115 at_risk_iids = self.services.issue.GetIIDsByLabelIDs( |
| 116 cnxn, at_risk_label_ids, project.project_id, shard_id) |
| 117 |
| 118 return at_risk_iids |
| 119 |
| 120 def GetPersonalAtRiskLabelIDs( |
| 121 self, cnxn, _user, effective_ids, project, perms): |
| 122 """Return list of label_ids for restriction labels that user can't view.""" |
| 123 at_risk_label_ids = [] |
| 124 label_def_rows = self.services.config.GetLabelDefRowsAnyProject( |
| 125 cnxn, where=[('LOWER(label) LIKE %s', [RESTRICT_VIEW_PATTERN])]) |
| 126 for label_id, _pid, _rank, label, _docstring, _hidden in label_def_rows: |
| 127 label_lower = label.lower() |
| 128 needed_perm = label_lower.split('-', 2)[-1] |
| 129 if not perms.CanUsePerm(needed_perm, effective_ids, project, []): |
| 130 at_risk_label_ids.append(label_id) |
| 131 |
| 132 return at_risk_label_ids |
| 133 |
| 134 def GetViewableIIDs(self, cnxn, effective_ids, project_id, shard_id): |
| 135 """Return IIDs of issues that user can view because they participate.""" |
| 136 # Anon user is never reporter, owner, CC'd or granted perms. |
| 137 if not effective_ids: |
| 138 return [] |
| 139 |
| 140 ok_iids = self.services.issue.GetIIDsByParticipant( |
| 141 cnxn, effective_ids, [project_id], shard_id) |
| 142 |
| 143 return ok_iids |
OLD | NEW |