1# Copyright 2015 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Adds pre hook to data store queries to hide internal-only data. 6 7Checks if the user has a google.com address, and hides data with the 8internal_only property set if not. 9""" 10 11import webapp2 12 13from google.appengine.api import apiproxy_stub_map 14from google.appengine.api import users 15from google.appengine.datastore import datastore_pb 16 17from dashboard import utils 18 19# The list below contains all kinds that have an internal_only property. 20# IMPORTANT: any new data types with internal_only properties must be added 21# here in order to be restricted to internal users. 22_INTERNAL_ONLY_KINDS = [ 23 'Bot', 24 'Test', 25 'Row', 26 'Sheriff', 27 'Anomaly', 28 'StoppageAlert', 29 'TryJob', 30] 31 32# Permissions namespaces. 33EXTERNAL = 'externally_visible' 34INTERNAL = 'internal_only' 35 36 37def InstallHooks(): 38 """Installs datastore pre hook to add access checks to queries. 39 40 This only needs to be called once, when doing config (currently in 41 appengine_config.py). 42 """ 43 apiproxy_stub_map.apiproxy.GetPreCallHooks().Push( 44 '_DatastorePreHook', _DatastorePreHook, 'datastore_v3') 45 46 47def SetPrivilegedRequest(): 48 """Allows the current request to act as a privileged user. 49 50 This should ONLY be called for handlers that are restricted from end users 51 by some other mechanism (IP whitelisting, admin-only pages). 52 53 This should be set once per request, before accessing the data store. 54 """ 55 request = webapp2.get_request() 56 request.registry['privileged'] = True 57 58 59def SetSinglePrivilegedRequest(): 60 """Allows the current request to act as a privileged user only ONCE. 61 62 This should be called ONLY by handlers that have checked privilege immediately 63 before making a query. It will be automatically unset when the next query is 64 made. 65 """ 66 request = webapp2.get_request() 67 request.registry['single_privileged'] = True 68 69 70def CancelSinglePrivilegedRequest(): 71 """Disallows the current request to act as a privileged user only.""" 72 request = webapp2.get_request() 73 request.registry['single_privileged'] = False 74 75 76def _IsServicingPrivilegedRequest(): 77 """Checks whether the request is considered privileged.""" 78 try: 79 request = webapp2.get_request() 80 except AssertionError: 81 # This happens in unit tests, when code gets called outside of a request. 82 return False 83 path = getattr(request, 'path', '') 84 if path.startswith('/mapreduce'): 85 return True 86 if path.startswith('/_ah/queue/deferred'): 87 return True 88 if path.startswith('/_ah/pipeline/'): 89 return True 90 if request.registry.get('privileged', False): 91 return True 92 if request.registry.get('single_privileged', False): 93 request.registry['single_privileged'] = False 94 return True 95 whitelist = utils.GetIpWhitelist() 96 if whitelist and hasattr(request, 'remote_addr'): 97 return request.remote_addr in whitelist 98 return False 99 100 101def IsUnalteredQueryPermitted(): 102 """Checks if the current user is internal, or the request is privileged. 103 104 "Internal users" are users whose email address belongs to a certain 105 privileged domain; but some privileged requests, such as task queue tasks, 106 are also considered privileged. 107 108 Returns: 109 True for users with google.com emails and privileged requests. 110 """ 111 if utils.IsInternalUser(): 112 return True 113 if users.is_current_user_admin(): 114 # It's possible to be an admin with a non-internal account; For example, 115 # the default login for dev appserver instances is test@example.com. 116 return True 117 return _IsServicingPrivilegedRequest() 118 119 120def GetNamespace(): 121 """Returns a namespace prefix string indicating internal or external user.""" 122 return INTERNAL if IsUnalteredQueryPermitted() else EXTERNAL 123 124 125def _DatastorePreHook(service, call, request, _): 126 """Adds a filter which checks whether to return internal data for queries. 127 128 If the user is not privileged, we don't want to return any entities that 129 have internal_only set to True. That is done here in a datastore hook. 130 See: https://developers.google.com/appengine/articles/hooks 131 132 Args: 133 service: Service name, must be 'datastore_v3'. 134 call: String representing function to call. One of 'Put', Get', 'Delete', 135 or 'RunQuery'. 136 request: Request protobuf. 137 _: Response protobuf (not used). 138 """ 139 assert service == 'datastore_v3' 140 if call != 'RunQuery': 141 return 142 if request.kind() not in _INTERNAL_ONLY_KINDS: 143 return 144 if IsUnalteredQueryPermitted(): 145 return 146 147 # Add a filter for internal_only == False, because the user is external. 148 try: 149 external_filter = request.filter_list().add() 150 except AttributeError: 151 # This is required to support proto1, which may be used by the unit tests. 152 # Later, if we don't need to support proto1, then this can be removed. 153 external_filter = request.add_filter() 154 external_filter.set_op(datastore_pb.Query_Filter.EQUAL) 155 new_property = external_filter.add_property() 156 new_property.set_name('internal_only') 157 new_property.mutable_value().set_booleanvalue(False) 158 new_property.set_multiple(False) 159