Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1 | # Copyright 2016 The Chromium Authors |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 4 | |
| 5 | """Servlet that searches for issues that the specified user cannot view. |
| 6 | |
| 7 | The GET request to a backend has query string parameters for the |
| 8 | shard_id, a user_id, and list of project IDs. It returns a |
| 9 | JSON-formatted dict with issue_ids that that user is not allowed to |
| 10 | view. As a side-effect, this servlet updates multiple entries |
| 11 | in memcache, including each "nonviewable:USER_ID;PROJECT_ID;SHARD_ID". |
| 12 | """ |
| 13 | from __future__ import print_function |
| 14 | from __future__ import division |
| 15 | from __future__ import absolute_import |
| 16 | |
| 17 | import logging |
| 18 | |
| 19 | from google.appengine.api import memcache |
| 20 | |
| 21 | import settings |
| 22 | from framework import authdata |
| 23 | from framework import framework_constants |
| 24 | from framework import framework_helpers |
| 25 | from framework import jsonfeed |
| 26 | from framework import permissions |
| 27 | from framework import sql |
| 28 | from search import search_helpers |
| 29 | |
| 30 | |
| 31 | |
| 32 | # We cache the set of IIDs that a given user cannot view, and we invalidate |
| 33 | # that set when the issues are changed via Monorail. Also, we limit the live |
| 34 | # those cache entries so that changes in a user's (direct or indirect) roles |
| 35 | # in a project will take effect. |
| 36 | NONVIEWABLE_MEMCACHE_EXPIRATION = 15 * framework_constants.SECS_PER_MINUTE |
| 37 | |
| 38 | |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 39 | class BackendNonviewable(jsonfeed.InternalTask): |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 40 | """JSON servlet for getting issue IDs that the specified user cannot view.""" |
| 41 | |
| 42 | CHECK_SAME_APP = True |
| 43 | |
| 44 | def HandleRequest(self, mr): |
| 45 | """Get all the user IDs that the specified user cannot view. |
| 46 | |
| 47 | Args: |
| 48 | mr: common information parsed from the HTTP request. |
| 49 | |
| 50 | Returns: |
| 51 | Results dictionary {project_id: [issue_id]} in JSON format. |
| 52 | """ |
| 53 | if mr.shard_id is None: |
| 54 | return {'message': 'Cannot proceed without a valid shard_id.'} |
| 55 | user_id = mr.specified_logged_in_user_id |
| 56 | auth = authdata.AuthData.FromUserID(mr.cnxn, user_id, self.services) |
| 57 | project_id = mr.specified_project_id |
| 58 | project = self.services.project.GetProject(mr.cnxn, project_id) |
| 59 | |
| 60 | perms = permissions.GetPermissions( |
| 61 | auth.user_pb, auth.effective_ids, project) |
| 62 | |
| 63 | nonviewable_iids = self.GetNonviewableIIDs( |
| 64 | mr.cnxn, auth.user_pb, auth.effective_ids, project, perms, mr.shard_id) |
| 65 | |
| 66 | cached_ts = mr.invalidation_timestep |
| 67 | if mr.specified_project_id: |
| 68 | memcache.set( |
| 69 | 'nonviewable:%d;%d;%d' % (project_id, user_id, mr.shard_id), |
| 70 | (nonviewable_iids, cached_ts), |
| 71 | time=NONVIEWABLE_MEMCACHE_EXPIRATION, |
| 72 | namespace=settings.memcache_namespace) |
| 73 | else: |
| 74 | memcache.set( |
| 75 | 'nonviewable:all;%d;%d' % (user_id, mr.shard_id), |
| 76 | (nonviewable_iids, cached_ts), |
| 77 | time=NONVIEWABLE_MEMCACHE_EXPIRATION, |
| 78 | namespace=settings.memcache_namespace) |
| 79 | |
| 80 | logging.info('set nonviewable:%s;%d;%d to %r', project_id, user_id, |
| 81 | mr.shard_id, nonviewable_iids) |
| 82 | |
| 83 | return { |
| 84 | 'nonviewable': nonviewable_iids, |
| 85 | |
| 86 | # These are not used in the frontend, but useful for debugging. |
| 87 | 'project_id': project_id, |
| 88 | 'user_id': user_id, |
| 89 | 'shard_id': mr.shard_id, |
| 90 | } |
| 91 | |
| 92 | def GetNonviewableIIDs( |
| 93 | self, cnxn, user, effective_ids, project, perms, shard_id): |
| 94 | """Return a list of IIDs that the user cannot view in the project shard.""" |
| 95 | # Project owners and site admins can see all issues. |
| 96 | if not perms.consider_restrictions: |
| 97 | return [] |
| 98 | |
| 99 | # There are two main parts to the computation that we do in parallel: |
| 100 | # getting at-risk IIDs and getting OK-iids. |
| 101 | cnxn_2 = sql.MonorailConnection() |
| 102 | at_risk_iids_promise = framework_helpers.Promise( |
| 103 | self.GetAtRiskIIDs, cnxn_2, user, effective_ids, project, perms, shard_id) |
| 104 | ok_iids = self.GetViewableIIDs( |
| 105 | cnxn, effective_ids, project.project_id, shard_id) |
| 106 | at_risk_iids = at_risk_iids_promise.WaitAndGetValue() |
| 107 | |
| 108 | # The set of non-viewable issues is the at-risk ones minus the ones where |
| 109 | # the user is the reporter, owner, CC'd, or granted "View" permission. |
| 110 | nonviewable_iids = set(at_risk_iids).difference(ok_iids) |
| 111 | |
| 112 | return list(nonviewable_iids) |
| 113 | |
| 114 | def GetAtRiskIIDs( |
| 115 | self, cnxn, user, effective_ids, project, perms, shard_id): |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 116 | # type: (MonorailConnection, mrproto.user_pb2.User, Sequence[int], Project, |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 117 | # permission_objects_pb2.PermissionSet, int) -> Sequence[int] |
| 118 | """Return IIDs of restricted issues that user might not be able to view.""" |
| 119 | at_risk_label_ids = search_helpers.GetPersonalAtRiskLabelIDs( |
| 120 | cnxn, user, self.services.config, effective_ids, project, perms) |
| 121 | at_risk_iids = self.services.issue.GetIIDsByLabelIDs( |
| 122 | cnxn, at_risk_label_ids, project.project_id, shard_id) |
| 123 | |
| 124 | return at_risk_iids |
| 125 | |
| 126 | |
| 127 | def GetViewableIIDs(self, cnxn, effective_ids, project_id, shard_id): |
| 128 | """Return IIDs of issues that user can view because they participate.""" |
| 129 | # Anon user is never reporter, owner, CC'd or granted perms. |
| 130 | if not effective_ids: |
| 131 | return [] |
| 132 | |
| 133 | ok_iids = self.services.issue.GetIIDsByParticipant( |
| 134 | cnxn, effective_ids, [project_id], shard_id) |
| 135 | |
| 136 | return ok_iids |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 137 | |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 138 | def GetBackendNonviewable(self, **kwargs): |
| 139 | return self.handler(**kwargs) |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 140 | |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 141 | def PostBackendNonviewable(self, **kwargs): |
| 142 | return self.handler(**kwargs) |