blob: 21f1189d09c15cbc5e3d4149b6783c10a24dc455 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# 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.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Servlet that searches for issues that the specified user cannot view.
6
7The GET request to a backend has query string parameters for the
8shard_id, a user_id, and list of project IDs. It returns a
9JSON-formatted dict with issue_ids that that user is not allowed to
10view. As a side-effect, this servlet updates multiple entries
11in memcache, including each "nonviewable:USER_ID;PROJECT_ID;SHARD_ID".
12"""
13from __future__ import print_function
14from __future__ import division
15from __future__ import absolute_import
16
17import logging
18
19from google.appengine.api import memcache
20
21import settings
22from framework import authdata
23from framework import framework_constants
24from framework import framework_helpers
25from framework import jsonfeed
26from framework import permissions
27from framework import sql
28from 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.
36NONVIEWABLE_MEMCACHE_EXPIRATION = 15 * framework_constants.SECS_PER_MINUTE
37
38
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010039class BackendNonviewable(jsonfeed.InternalTask):
Copybara854996b2021-09-07 19:36:02 +000040 """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ínezf19ea432024-01-23 20:20:52 +0100116 # type: (MonorailConnection, mrproto.user_pb2.User, Sequence[int], Project,
Copybara854996b2021-09-07 19:36:02 +0000117 # 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ínezde942802022-07-15 14:06:55 +0200137
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200138 def GetBackendNonviewable(self, **kwargs):
139 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200140
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200141 def PostBackendNonviewable(self, **kwargs):
142 return self.handler(**kwargs)