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