/Users/deen/code/yugabyte-db/src/yb/client/meta_data_cache.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #include "yb/client/meta_data_cache.h" |
15 | | |
16 | | #include "yb/client/client.h" |
17 | | #include "yb/client/permissions.h" |
18 | | #include "yb/client/table.h" |
19 | | #include "yb/client/yb_table_name.h" |
20 | | |
21 | | #include "yb/common/roles_permissions.h" |
22 | | |
23 | | #include "yb/util/result.h" |
24 | | #include "yb/util/status_format.h" |
25 | | #include "yb/util/status_log.h" |
26 | | |
27 | | DEFINE_int32(update_permissions_cache_msecs, 2000, |
28 | | "How often the roles' permissions cache should be updated. 0 means never update it"); |
29 | | |
30 | | namespace yb { |
31 | | namespace client { |
32 | | |
33 | | namespace { |
34 | | |
35 | | Status GenerateUnauthorizedError(const std::string& canonical_resource, |
36 | | const ql::ObjectType& object_type, |
37 | | const RoleName& role_name, |
38 | | const PermissionType& permission, |
39 | | const NamespaceName& keyspace, |
40 | 843 | const TableName& table) { |
41 | 843 | switch (object_type) { |
42 | 245 | case ql::ObjectType::TABLE: |
43 | 245 | return STATUS_SUBSTITUTE(NotAuthorized, |
44 | 0 | "User $0 has no $1 permission on <table $2.$3> or any of its parents", |
45 | 0 | role_name, PermissionName(permission), keyspace, table); |
46 | 562 | case ql::ObjectType::SCHEMA: |
47 | 562 | if (canonical_resource == "data") { |
48 | 88 | return STATUS_SUBSTITUTE(NotAuthorized, |
49 | 88 | "User $0 has no $1 permission on <all keyspaces> or any of its parents", |
50 | 88 | role_name, PermissionName(permission)); |
51 | 88 | } |
52 | 474 | return STATUS_SUBSTITUTE(NotAuthorized, |
53 | 474 | "User $0 has no $1 permission on <keyspace $2> or any of its parents", |
54 | 474 | role_name, PermissionName(permission), keyspace); |
55 | 36 | case ql::ObjectType::ROLE: |
56 | 36 | if (canonical_resource == "role") { |
57 | 0 | return STATUS_SUBSTITUTE(NotAuthorized, |
58 | 0 | "User $0 has no $1 permission on <all roles> or any of its parents", |
59 | 0 | role_name, PermissionName(permission)); |
60 | 0 | } |
61 | 36 | return STATUS_SUBSTITUTE(NotAuthorized, |
62 | 36 | "User $0 does not have sufficient privileges to perform the requested operation", |
63 | 36 | role_name); |
64 | 0 | default: |
65 | 0 | return STATUS_SUBSTITUTE(IllegalState, "Unable to find permissions for object $0", |
66 | 843 | to_underlying(object_type)); |
67 | 843 | } |
68 | 843 | } |
69 | | |
70 | | } // namespace |
71 | | |
72 | | YBMetaDataCache::YBMetaDataCache(client::YBClient* client, |
73 | 43.3k | bool create_roles_permissions_cache) : client_(client) { |
74 | 43.3k | if (create_roles_permissions_cache) { |
75 | 1.45k | permissions_cache_ = std::make_shared<client::internal::PermissionsCache>(client); |
76 | 41.8k | } else { |
77 | 41.8k | LOG(INFO) << "Creating a metadata cache without a permissions cache"; |
78 | 41.8k | } |
79 | 43.3k | } |
80 | | |
81 | 39.4k | YBMetaDataCache::~YBMetaDataCache() = default; |
82 | | |
83 | | Status YBMetaDataCache::GetTable(const YBTableName& table_name, |
84 | | std::shared_ptr<YBTable>* table, |
85 | 532k | bool* cache_used) { |
86 | 532k | { |
87 | 532k | std::lock_guard<std::mutex> lock(cached_tables_mutex_); |
88 | 532k | auto itr = cached_tables_by_name_.find(table_name); |
89 | 532k | if (itr != cached_tables_by_name_.end()) { |
90 | 487k | *table = itr->second; |
91 | 487k | *cache_used = true; |
92 | 487k | return Status::OK(); |
93 | 487k | } |
94 | 44.7k | } |
95 | | |
96 | 44.7k | RETURN_NOT_OK(client_->OpenTable(table_name, table)); |
97 | 42.4k | { |
98 | 42.4k | std::lock_guard<std::mutex> lock(cached_tables_mutex_); |
99 | 42.4k | cached_tables_by_name_[(*table)->name()] = *table; |
100 | 42.4k | cached_tables_by_id_[(*table)->id()] = *table; |
101 | 42.4k | } |
102 | 42.4k | *cache_used = false; |
103 | 42.4k | return Status::OK(); |
104 | 44.7k | } |
105 | | |
106 | | Status YBMetaDataCache::GetTable(const TableId& table_id, |
107 | | std::shared_ptr<YBTable>* table, |
108 | 49.4k | bool* cache_used) { |
109 | 49.4k | { |
110 | 49.4k | std::lock_guard<std::mutex> lock(cached_tables_mutex_); |
111 | 49.4k | auto itr = cached_tables_by_id_.find(table_id); |
112 | 49.4k | if (itr != cached_tables_by_id_.end()) { |
113 | 47.4k | *table = itr->second; |
114 | 47.4k | *cache_used = true; |
115 | 47.4k | return Status::OK(); |
116 | 47.4k | } |
117 | 1.94k | } |
118 | | |
119 | 1.94k | RETURN_NOT_OK(client_->OpenTable(table_id, table)); |
120 | 1.94k | { |
121 | 1.94k | std::lock_guard<std::mutex> lock(cached_tables_mutex_); |
122 | 1.94k | cached_tables_by_name_[(*table)->name()] = *table; |
123 | 1.94k | cached_tables_by_id_[table_id] = *table; |
124 | 1.94k | } |
125 | 1.94k | *cache_used = false; |
126 | 1.94k | return Status::OK(); |
127 | 1.94k | } |
128 | | |
129 | 9.24k | void YBMetaDataCache::RemoveCachedTable(const YBTableName& table_name) { |
130 | 9.24k | std::lock_guard<std::mutex> lock(cached_tables_mutex_); |
131 | 9.24k | const auto itr = cached_tables_by_name_.find(table_name); |
132 | 9.24k | if (itr != cached_tables_by_name_.end()) { |
133 | 2.88k | const auto table_id = itr->second->id(); |
134 | 2.88k | cached_tables_by_name_.erase(itr); |
135 | 2.88k | cached_tables_by_id_.erase(table_id); |
136 | 2.88k | } |
137 | 9.24k | } |
138 | | |
139 | 0 | void YBMetaDataCache::RemoveCachedTable(const TableId& table_id) { |
140 | 0 | std::lock_guard<std::mutex> lock(cached_tables_mutex_); |
141 | 0 | const auto itr = cached_tables_by_id_.find(table_id); |
142 | 0 | if (itr != cached_tables_by_id_.end()) { |
143 | 0 | const auto table_name = itr->second->name(); |
144 | 0 | cached_tables_by_name_.erase(table_name); |
145 | 0 | cached_tables_by_id_.erase(itr); |
146 | 0 | } |
147 | 0 | } |
148 | | |
149 | | Status YBMetaDataCache::GetUDType(const string& keyspace_name, |
150 | | const string& type_name, |
151 | | std::shared_ptr<QLType> *type, |
152 | 81 | bool *cache_used) { |
153 | 81 | auto type_path = std::make_pair(keyspace_name, type_name); |
154 | 81 | { |
155 | 81 | std::lock_guard<std::mutex> lock(cached_types_mutex_); |
156 | 81 | auto itr = cached_types_.find(type_path); |
157 | 81 | if (itr != cached_types_.end()) { |
158 | 26 | *type = itr->second; |
159 | 26 | *cache_used = true; |
160 | 26 | return Status::OK(); |
161 | 26 | } |
162 | 55 | } |
163 | | |
164 | 55 | RETURN_NOT_OK(client_->GetUDType(keyspace_name, type_name, type)); |
165 | 48 | { |
166 | 48 | std::lock_guard<std::mutex> lock(cached_types_mutex_); |
167 | 48 | cached_types_[type_path] = *type; |
168 | 48 | } |
169 | 48 | *cache_used = false; |
170 | 48 | return Status::OK(); |
171 | 55 | } |
172 | | |
173 | | void YBMetaDataCache::RemoveCachedUDType(const string& keyspace_name, |
174 | 59 | const string& type_name) { |
175 | 59 | std::lock_guard<std::mutex> lock(cached_types_mutex_); |
176 | 59 | cached_types_.erase(std::make_pair(keyspace_name, type_name)); |
177 | 59 | } |
178 | | |
179 | 917 | Status YBMetaDataCache::WaitForPermissionCache() { |
180 | 917 | if (!permissions_cache_) { |
181 | 0 | LOG(WARNING) << "Permissions cache disabled. This only should be used in unit tests"; |
182 | 0 | return STATUS(TimedOut, "Permissions cache unavailable"); |
183 | 0 | } |
184 | | |
185 | 917 | if (!permissions_cache_->ready()) { |
186 | 0 | if (!permissions_cache_->WaitUntilReady( |
187 | 0 | MonoDelta::FromMilliseconds(FLAGS_update_permissions_cache_msecs))) { |
188 | 0 | return STATUS(TimedOut, "Permissions cache unavailable"); |
189 | 0 | } |
190 | 917 | } |
191 | 917 | return Status::OK(); |
192 | 917 | } |
193 | | |
194 | 519 | Result<std::string> YBMetaDataCache::RoleSaltedHash(const RoleName& role_name) { |
195 | 519 | RETURN_NOT_OK(WaitForPermissionCache()); |
196 | 519 | return permissions_cache_->salted_hash(role_name); |
197 | 519 | } |
198 | | |
199 | 398 | Result<bool> YBMetaDataCache::RoleCanLogin(const RoleName& role_name) { |
200 | 398 | RETURN_NOT_OK(WaitForPermissionCache()); |
201 | 398 | return permissions_cache_->can_login(role_name); |
202 | 398 | } |
203 | | |
204 | | Status YBMetaDataCache::HasResourcePermission(const std::string& canonical_resource, |
205 | | const ql::ObjectType& object_type, |
206 | | const RoleName& role_name, |
207 | | const PermissionType& permission, |
208 | | const NamespaceName& keyspace, |
209 | | const TableName& table, |
210 | 13.6k | const CacheCheckMode check_mode) { |
211 | 13.6k | if (!permissions_cache_) { |
212 | 0 | LOG(WARNING) << "Permissions cache disabled. This only should be used in unit tests"; |
213 | 0 | return Status::OK(); |
214 | 0 | } |
215 | | |
216 | 13.6k | if (object_type != ql::ObjectType::SCHEMA && |
217 | 4.11k | object_type != ql::ObjectType::TABLE && |
218 | 3.70k | object_type != ql::ObjectType::ROLE) { |
219 | 0 | DFATAL_OR_RETURN_NOT_OK(STATUS_SUBSTITUTE(InvalidArgument, "Invalid ObjectType $0", |
220 | 0 | to_underlying(object_type))); |
221 | 0 | } |
222 | | |
223 | 13.6k | if (!permissions_cache_->ready()) { |
224 | 0 | if (!permissions_cache_->WaitUntilReady( |
225 | 0 | MonoDelta::FromMilliseconds(FLAGS_update_permissions_cache_msecs))) { |
226 | 0 | return STATUS(TimedOut, "Permissions cache unavailable"); |
227 | 0 | } |
228 | 13.6k | } |
229 | | |
230 | 13.6k | if (!permissions_cache_->HasCanonicalResourcePermission(canonical_resource, object_type, |
231 | 1.06k | role_name, permission)) { |
232 | 1.06k | if (check_mode == CacheCheckMode::RETRY) { |
233 | | // We could have failed to find the permission because our cache is stale. If we are asked |
234 | | // to retry, we update the cache and try again. |
235 | 538 | RETURN_NOT_OK(client_->GetPermissions(permissions_cache_.get())); |
236 | 538 | if (permissions_cache_->HasCanonicalResourcePermission(canonical_resource, object_type, |
237 | 222 | role_name, permission)) { |
238 | 222 | return Status::OK(); |
239 | 222 | } |
240 | 843 | } |
241 | 843 | return GenerateUnauthorizedError( |
242 | 843 | canonical_resource, object_type, role_name, permission, keyspace, table); |
243 | 843 | } |
244 | | |
245 | | // Found. |
246 | 12.5k | return Status::OK(); |
247 | 12.5k | } |
248 | | |
249 | | Status YBMetaDataCache::HasTablePermission(const NamespaceName& keyspace_name, |
250 | | const TableName& table_name, |
251 | | const RoleName& role_name, |
252 | | const PermissionType permission, |
253 | 4.42k | const CacheCheckMode check_mode) { |
254 | | |
255 | | // Check wihtout retry. In case our cache is stale, we will check again by issuing a recursive |
256 | | // call to this method. |
257 | 4.42k | if (HasResourcePermission(get_canonical_keyspace(keyspace_name), |
258 | 4.42k | ql::ObjectType::SCHEMA, role_name, permission, |
259 | 4.00k | keyspace_name, "", CacheCheckMode::NO_RETRY).ok()) { |
260 | 4.00k | return Status::OK(); |
261 | 4.00k | } |
262 | | |
263 | | // By default the first call asks to retry. If we decide to retry, we will issue a recursive |
264 | | // call with NO_RETRY mode. |
265 | 415 | Status s = HasResourcePermission(get_canonical_table(keyspace_name, table_name), |
266 | 415 | ql::ObjectType::TABLE, role_name, permission, |
267 | 415 | keyspace_name, table_name, |
268 | 415 | check_mode); |
269 | | |
270 | 415 | if (check_mode == CacheCheckMode::RETRY && s.IsNotAuthorized()) { |
271 | 133 | s = HasTablePermission(keyspace_name, table_name, role_name, permission, |
272 | 133 | CacheCheckMode::NO_RETRY); |
273 | 133 | } |
274 | 415 | return s; |
275 | 415 | } |
276 | | |
277 | | } // namespace client |
278 | | } // namespace yb |