Line data Source code
1 : /**
2 : * Copyright Soramitsu Co., Ltd. All Rights Reserved.
3 : * SPDX-License-Identifier: Apache-2.0
4 : */
5 :
6 : #include "ametsuchi/impl/postgres_query_executor.hpp"
7 :
8 : #include <boost-tuple.h>
9 : #include <soci/boost-tuple.h>
10 : #include <soci/postgresql/soci-postgresql.h>
11 : #include <boost/algorithm/string.hpp>
12 : #include <boost/format.hpp>
13 : #include <boost/range/adaptor/filtered.hpp>
14 : #include <boost/range/adaptor/transformed.hpp>
15 : #include <boost/range/algorithm/for_each.hpp>
16 : #include <boost/range/algorithm/transform.hpp>
17 : #include <boost/range/irange.hpp>
18 :
19 : #include "ametsuchi/impl/soci_utils.hpp"
20 : #include "common/byteutils.hpp"
21 : #include "cryptography/public_key.hpp"
22 : #include "interfaces/queries/blocks_query.hpp"
23 : #include "interfaces/queries/get_account.hpp"
24 : #include "interfaces/queries/get_account_asset_transactions.hpp"
25 : #include "interfaces/queries/get_account_assets.hpp"
26 : #include "interfaces/queries/get_account_detail.hpp"
27 : #include "interfaces/queries/get_account_transactions.hpp"
28 : #include "interfaces/queries/get_asset_info.hpp"
29 : #include "interfaces/queries/get_block.hpp"
30 : #include "interfaces/queries/get_pending_transactions.hpp"
31 : #include "interfaces/queries/get_role_permissions.hpp"
32 : #include "interfaces/queries/get_roles.hpp"
33 : #include "interfaces/queries/get_signatories.hpp"
34 : #include "interfaces/queries/get_transactions.hpp"
35 : #include "interfaces/queries/query.hpp"
36 : #include "interfaces/queries/tx_pagination_meta.hpp"
37 : #include "logger/logger.hpp"
38 : #include "logger/logger_manager.hpp"
39 :
40 : using namespace shared_model::interface::permissions;
41 :
42 : namespace {
43 :
44 : using namespace iroha;
45 :
46 : shared_model::interface::types::DomainIdType getDomainFromName(
47 : const shared_model::interface::types::AccountIdType &account_id) {
48 : // TODO 03.10.18 andrei: IR-1728 Move getDomainFromName to shared_model
49 250 : std::vector<std::string> res;
50 250 : boost::split(res, account_id, boost::is_any_of("@"));
51 250 : return res.at(1);
52 250 : }
53 :
54 : std::string getAccountRolePermissionCheckSql(
55 : shared_model::interface::permissions::Role permission,
56 : const std::string &account_alias = "role_account_id") {
57 : const auto perm_str =
58 87 : shared_model::interface::RolePermissionSet({permission}).toBitstring();
59 87 : const auto bits = shared_model::interface::RolePermissionSet::size();
60 : // TODO 14.09.18 andrei: IR-1708 Load SQL from separate files
61 91 : std::string query = (boost::format(R"(
62 : SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
63 : & '%2%') = '%2%' AS perm FROM role_has_permissions AS rp
64 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
65 : WHERE ar.account_id = :%3%)")
66 88 : % bits % perm_str % account_alias)
67 89 : .str();
68 91 : return query;
69 91 : }
70 :
71 : /**
72 : * Generate an SQL subquery which checks if creator has corresponding
73 : * permissions for target account
74 : * It verifies individual, domain, and global permissions, and returns true if
75 : * any of listed permissions is present
76 : */
77 : auto hasQueryPermission(
78 : const shared_model::interface::types::AccountIdType &creator,
79 : const shared_model::interface::types::AccountIdType &target_account,
80 : Role indiv_permission_id,
81 : Role all_permission_id,
82 : Role domain_permission_id) {
83 125 : const auto bits = shared_model::interface::RolePermissionSet::size();
84 : const auto perm_str =
85 125 : shared_model::interface::RolePermissionSet({indiv_permission_id})
86 125 : .toBitstring();
87 : const auto all_perm_str =
88 125 : shared_model::interface::RolePermissionSet({all_permission_id})
89 125 : .toBitstring();
90 : const auto domain_perm_str =
91 125 : shared_model::interface::RolePermissionSet({domain_permission_id})
92 125 : .toBitstring();
93 :
94 125 : boost::format cmd(R"(
95 : WITH
96 : has_indiv_perm AS (
97 : SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
98 : & '%3%') = '%3%' FROM role_has_permissions AS rp
99 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
100 : WHERE ar.account_id = '%2%'
101 : ),
102 : has_all_perm AS (
103 : SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
104 : & '%4%') = '%4%' FROM role_has_permissions AS rp
105 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
106 : WHERE ar.account_id = '%2%'
107 : ),
108 : has_domain_perm AS (
109 : SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
110 : & '%5%') = '%5%' FROM role_has_permissions AS rp
111 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
112 : WHERE ar.account_id = '%2%'
113 : )
114 : SELECT ('%2%' = '%6%' AND (SELECT * FROM has_indiv_perm))
115 : OR (SELECT * FROM has_all_perm)
116 : OR ('%7%' = '%8%' AND (SELECT * FROM has_domain_perm)) AS perm
117 : )");
118 :
119 125 : return (cmd % bits % creator % perm_str % all_perm_str % domain_perm_str
120 125 : % target_account % getDomainFromName(creator)
121 125 : % getDomainFromName(target_account))
122 125 : .str();
123 125 : }
124 :
125 : /// Query result is a tuple of optionals, since there could be no entry
126 : template <typename... Value>
127 : using QueryType = boost::tuple<boost::optional<Value>...>;
128 :
129 : /**
130 : * Create an error response in case user does not have permissions to perform
131 : * a query
132 : * @tparam Roles - type of roles
133 : * @param roles, which user lacks
134 : * @return lambda returning the error response itself
135 : */
136 : template <typename... Roles>
137 : auto notEnoughPermissionsResponse(
138 : std::shared_ptr<shared_model::interface::PermissionToString>
139 : perm_converter,
140 : Roles... roles) {
141 : return [perm_converter, roles...] {
142 43 : std::string error = "user must have at least one of the permissions: ";
143 172 : for (auto role : {roles...}) {
144 129 : error += perm_converter->toString(role) + ", ";
145 : }
146 43 : return error;
147 43 : };
148 : }
149 :
150 : } // namespace
151 :
152 : namespace iroha {
153 : namespace ametsuchi {
154 :
155 : template <typename RangeGen, typename Pred>
156 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
157 : PostgresQueryExecutorVisitor::getTransactionsFromBlock(uint64_t block_id,
158 : RangeGen &&range_gen,
159 : Pred &&pred) {
160 27 : std::vector<std::unique_ptr<shared_model::interface::Transaction>> result;
161 27 : auto serialized_block = block_store_.get(block_id);
162 31 : if (not serialized_block) {
163 0 : log_->error("Failed to retrieve block with id {}", block_id);
164 0 : return result;
165 : }
166 : auto deserialized_block =
167 31 : converter_->deserialize(bytesToString(*serialized_block));
168 : // boost::get of pointer returns pointer to requested type, or nullptr
169 31 : if (auto e =
170 31 : boost::get<expected::Error<std::string>>(&deserialized_block)) {
171 0 : log_->error(e->error);
172 0 : return result;
173 : }
174 :
175 31 : auto &block =
176 31 : boost::get<
177 : expected::Value<std::unique_ptr<shared_model::interface::Block>>>(
178 31 : deserialized_block)
179 31 : .value;
180 :
181 31 : boost::transform(range_gen(boost::size(block->transactions()))
182 31 : | boost::adaptors::transformed(
183 : [&block](auto i) -> decltype(auto) {
184 66 : return block->transactions()[i];
185 0 : })
186 31 : | boost::adaptors::filtered(pred),
187 31 : std::back_inserter(result),
188 : [&](const auto &tx) { return clone(tx); });
189 :
190 31 : return result;
191 31 : }
192 :
193 : template <typename QueryTuple,
194 : typename PermissionTuple,
195 : typename QueryExecutor,
196 : typename ResponseCreator,
197 : typename PermissionsErrResponse>
198 : QueryExecutorResult PostgresQueryExecutorVisitor::executeQuery(
199 : QueryExecutor &&query_executor,
200 : ResponseCreator &&response_creator,
201 : PermissionsErrResponse &&perms_err_response) {
202 : using T = concat<QueryTuple, PermissionTuple>;
203 : try {
204 34 : soci::rowset<T> st = std::forward<QueryExecutor>(query_executor)();
205 34 : auto range = boost::make_iterator_range(st.begin(), st.end());
206 :
207 30 : return apply(
208 34 : viewPermissions<PermissionTuple>(range.front()),
209 : [this, range, &response_creator, &perms_err_response](
210 : auto... perms) {
211 34 : bool temp[] = {not perms...};
212 : if (std::all_of(std::begin(temp), std::end(temp), [](auto b) {
213 38 : return b;
214 : })) {
215 : // TODO [IR-1816] Akvinikym 03.12.18: replace magic number 2
216 : // with a named constant
217 8 : return this->logAndReturnErrorResponse(
218 : QueryErrorType::kStatefulFailed,
219 8 : std::forward<PermissionsErrResponse>(perms_err_response)(),
220 : 2);
221 : }
222 33 : auto query_range = range
223 : | boost::adaptors::transformed([](auto &t) {
224 89 : return rebind(viewQuery<QueryTuple>(t));
225 0 : })
226 : | boost::adaptors::filtered([](const auto &t) {
227 45 : return static_cast<bool>(t);
228 : })
229 : | boost::adaptors::transformed([](auto t) { return *t; });
230 33 : return std::forward<ResponseCreator>(response_creator)(
231 33 : query_range, perms...);
232 34 : });
233 34 : } catch (const std::exception &e) {
234 0 : return this->logAndReturnErrorResponse(
235 0 : QueryErrorType::kStatefulFailed, e.what(), 1);
236 0 : }
237 34 : }
238 :
239 : template <class Q>
240 : bool PostgresQueryExecutor::validateSignatures(const Q &query) {
241 : auto keys_range =
242 : query.signatures() | boost::adaptors::transformed([](const auto &s) {
243 133 : return s.publicKey().hex();
244 : });
245 :
246 132 : if (boost::size(keys_range) != 1) {
247 0 : return false;
248 : }
249 132 : std::string keys = *std::begin(keys_range);
250 : // not using bool since it is not supported by SOCI
251 132 : boost::optional<uint8_t> signatories_valid;
252 :
253 132 : auto qry = R"(
254 : SELECT count(public_key) = 1
255 : FROM account_has_signatory
256 : WHERE account_id = :account_id AND public_key = :pk
257 : )";
258 :
259 : try {
260 132 : *sql_ << qry, soci::into(signatories_valid),
261 133 : soci::use(query.creatorAccountId(), "account_id"),
262 133 : soci::use(keys, "pk");
263 132 : } catch (const std::exception &e) {
264 0 : log_->error(e.what());
265 0 : return false;
266 0 : }
267 :
268 133 : return signatories_valid and *signatories_valid;
269 133 : }
270 :
271 : PostgresQueryExecutor::PostgresQueryExecutor(
272 : std::unique_ptr<soci::session> sql,
273 : KeyValueStorage &block_store,
274 : std::shared_ptr<PendingTransactionStorage> pending_txs_storage,
275 : std::shared_ptr<shared_model::interface::BlockJsonConverter> converter,
276 : std::shared_ptr<shared_model::interface::QueryResponseFactory>
277 : response_factory,
278 : std::shared_ptr<shared_model::interface::PermissionToString>
279 : perm_converter,
280 : logger::LoggerManagerTreePtr log_manager)
281 192 : : sql_(std::move(sql)),
282 192 : block_store_(block_store),
283 192 : pending_txs_storage_(std::move(pending_txs_storage)),
284 192 : visitor_(*sql_,
285 192 : block_store_,
286 192 : pending_txs_storage_,
287 192 : std::move(converter),
288 192 : response_factory,
289 192 : perm_converter,
290 192 : log_manager->getChild("Visitor")->getLogger()),
291 192 : query_response_factory_{std::move(response_factory)},
292 192 : log_(log_manager->getLogger()) {}
293 :
294 : QueryExecutorResult PostgresQueryExecutor::validateAndExecute(
295 : const shared_model::interface::Query &query,
296 : const bool validate_signatories = true) {
297 190 : visitor_.setCreatorId(query.creatorAccountId());
298 190 : visitor_.setQueryHash(query.hash());
299 190 : if (validate_signatories and not validateSignatures(query)) {
300 : // TODO [IR-1816] Akvinikym 03.12.18: replace magic number 3
301 : // with a named constant
302 3 : return query_response_factory_->createErrorQueryResponse(
303 : shared_model::interface::QueryResponseFactory::ErrorQueryType::
304 : kStatefulFailed,
305 3 : "query signatories did not pass validation",
306 : 3,
307 3 : query.hash());
308 : }
309 187 : return boost::apply_visitor(visitor_, query.get());
310 190 : }
311 :
312 : bool PostgresQueryExecutor::validate(
313 : const shared_model::interface::BlocksQuery &query,
314 : const bool validate_signatories = true) {
315 2 : if (validate_signatories and not validateSignatures(query)) {
316 0 : log_->error("query signatories did not pass validation");
317 0 : return false;
318 : }
319 2 : if (not visitor_.hasAccountRolePermission(Role::kGetBlocks,
320 2 : query.creatorAccountId())) {
321 1 : log_->error("query creator does not have enough permissions");
322 1 : return false;
323 : }
324 :
325 1 : return true;
326 2 : }
327 :
328 : bool PostgresQueryExecutorVisitor::hasAccountRolePermission(
329 : shared_model::interface::permissions::Role permission,
330 : const std::string &account_id) const {
331 : using T = boost::tuple<int>;
332 5 : boost::format cmd(R"(%s)");
333 : try {
334 5 : soci::rowset<T> st =
335 5 : (sql_.prepare
336 5 : << (cmd % getAccountRolePermissionCheckSql(permission)).str(),
337 5 : soci::use(account_id, "role_account_id"));
338 5 : return st.begin()->get<0>();
339 5 : } catch (const std::exception &e) {
340 0 : log_->error("Failed to validate query: {}", e.what());
341 0 : return false;
342 0 : }
343 5 : }
344 :
345 : PostgresQueryExecutorVisitor::PostgresQueryExecutorVisitor(
346 : soci::session &sql,
347 : KeyValueStorage &block_store,
348 : std::shared_ptr<PendingTransactionStorage> pending_txs_storage,
349 : std::shared_ptr<shared_model::interface::BlockJsonConverter> converter,
350 : std::shared_ptr<shared_model::interface::QueryResponseFactory>
351 : response_factory,
352 : std::shared_ptr<shared_model::interface::PermissionToString>
353 : perm_converter,
354 : logger::LoggerPtr log)
355 192 : : sql_(sql),
356 192 : block_store_(block_store),
357 192 : pending_txs_storage_(std::move(pending_txs_storage)),
358 192 : converter_(std::move(converter)),
359 192 : query_response_factory_{std::move(response_factory)},
360 192 : perm_converter_(std::move(perm_converter)),
361 192 : log_(std::move(log)) {}
362 :
363 : void PostgresQueryExecutorVisitor::setCreatorId(
364 : const shared_model::interface::types::AccountIdType &creator_id) {
365 190 : creator_id_ = creator_id;
366 190 : }
367 :
368 : void PostgresQueryExecutorVisitor::setQueryHash(
369 : const shared_model::interface::types::HashType &query_hash) {
370 190 : query_hash_ = query_hash;
371 190 : }
372 :
373 : std::unique_ptr<shared_model::interface::QueryResponse>
374 : PostgresQueryExecutorVisitor::logAndReturnErrorResponse(
375 : QueryErrorType error_type,
376 : QueryErrorMessageType error_body,
377 : QueryErrorCodeType error_code) const {
378 64 : std::string error;
379 64 : switch (error_type) {
380 : case QueryErrorType::kNoAccount:
381 1 : error = "could find account with such id: " + error_body;
382 1 : break;
383 : case QueryErrorType::kNoSignatories:
384 1 : error = "no signatories found in account with such id: " + error_body;
385 1 : break;
386 : case QueryErrorType::kNoAccountDetail:
387 1 : error = "no details in account with such id: " + error_body;
388 1 : break;
389 : case QueryErrorType::kNoRoles:
390 1 : error =
391 1 : "no role with such name in account with such id: " + error_body;
392 1 : break;
393 : case QueryErrorType::kNoAsset:
394 2 : error =
395 2 : "no asset with such name in account with such id: " + error_body;
396 2 : break;
397 : // other errors are either handled by generic response or do not
398 : // appear yet
399 : default:
400 58 : error = "failed to execute query: " + error_body;
401 58 : break;
402 : }
403 :
404 64 : log_->error("{}", error);
405 64 : return query_response_factory_->createErrorQueryResponse(
406 64 : error_type, error, error_code, query_hash_);
407 64 : }
408 :
409 : template <typename Query,
410 : typename QueryChecker,
411 : typename QueryApplier,
412 : typename... Permissions>
413 : QueryExecutorResult PostgresQueryExecutorVisitor::executeTransactionsQuery(
414 : const Query &q,
415 : QueryChecker &&qry_checker,
416 : const std::string &related_txs,
417 : QueryApplier applier,
418 : Permissions... perms) {
419 : using QueryTuple = QueryType<shared_model::interface::types::HeightType,
420 : uint64_t,
421 : uint64_t>;
422 : using PermissionTuple = boost::tuple<int>;
423 23 : const auto &pagination_info = q.paginationMeta();
424 23 : auto first_hash = pagination_info.firstTxHash();
425 : // retrieve one extra transaction to populate next_hash
426 23 : auto query_size = pagination_info.pageSize() + 1u;
427 :
428 23 : auto base = boost::format(R"(WITH has_perms AS (%s),
429 : my_txs AS (%s),
430 : first_hash AS (%s),
431 : total_size AS (
432 : SELECT COUNT(*) FROM my_txs
433 : ),
434 : t AS (
435 : SELECT my_txs.height, my_txs.index
436 : FROM my_txs JOIN
437 : first_hash ON my_txs.height > first_hash.height
438 : OR (my_txs.height = first_hash.height AND
439 : my_txs.index >= first_hash.index)
440 : LIMIT :page_size
441 : )
442 : SELECT height, index, count, perm FROM t
443 : RIGHT OUTER JOIN has_perms ON TRUE
444 : JOIN total_size ON TRUE
445 : )");
446 :
447 : // select tx with specified hash
448 23 : auto first_by_hash = R"(SELECT height, index FROM position_by_hash
449 : WHERE hash = :hash LIMIT 1)";
450 :
451 : // select first ever tx
452 23 : auto first_tx = R"(SELECT height, index FROM position_by_hash
453 : ORDER BY height, index ASC LIMIT 1)";
454 :
455 23 : auto cmd = base % hasQueryPermission(creator_id_, q.accountId(), perms...)
456 23 : % related_txs;
457 23 : if (first_hash) {
458 2 : cmd = base % first_by_hash;
459 2 : } else {
460 21 : cmd = base % first_tx;
461 : }
462 :
463 23 : auto query = cmd.str();
464 :
465 23 : return executeQuery<QueryTuple, PermissionTuple>(
466 23 : applier(query),
467 : [&](auto range, auto &) {
468 16 : uint64_t total_size = 0;
469 16 : if (not boost::empty(range)) {
470 12 : total_size = boost::get<2>(*range.begin());
471 12 : }
472 16 : std::map<uint64_t, std::vector<uint64_t>> index;
473 : // unpack results to get map from block height to index of tx in
474 : // a block
475 : boost::for_each(range, [&index](auto t) {
476 : apply(t, [&index](auto &height, auto &idx, auto &) {
477 33 : index[height].push_back(idx);
478 33 : });
479 33 : });
480 :
481 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
482 16 : response_txs;
483 : // get transactions corresponding to indexes
484 42 : for (auto &block : index) {
485 27 : auto txs = this->getTransactionsFromBlock(
486 27 : block.first,
487 : [&block](auto) { return block.second; },
488 : [](auto &) { return true; });
489 27 : std::move(
490 27 : txs.begin(), txs.end(), std::back_inserter(response_txs));
491 27 : }
492 :
493 16 : if (response_txs.empty()) {
494 4 : if (first_hash) {
495 : // if 0 transactions are returned, and there is a specified
496 : // paging hash, we assume it's invalid, since query with valid
497 : // hash is guaranteed to return at least one transaction
498 1 : auto error = (boost::format("invalid pagination hash: %s")
499 1 : % first_hash->hex())
500 1 : .str();
501 1 : return this->logAndReturnErrorResponse(
502 1 : QueryErrorType::kStatefulFailed, error, 4);
503 1 : }
504 : // if paging hash is not specified, we should check, why 0
505 : // transactions are returned - it can be because there are
506 : // actually no transactions for this query or some of the
507 : // parameters were wrong
508 3 : if (auto query_incorrect =
509 23 : std::forward<QueryChecker>(qry_checker)(q)) {
510 2 : return this->logAndReturnErrorResponse(
511 : QueryErrorType::kStatefulFailed,
512 2 : query_incorrect.error_message,
513 2 : query_incorrect.error_code);
514 : }
515 1 : }
516 :
517 : // if the number of returned transactions is equal to the
518 : // page size + 1, it means that the last transaction is the
519 : // first one in the next page and we need to return it as
520 : // the next hash
521 13 : if (response_txs.size() == query_size) {
522 1 : auto next_hash = response_txs.back()->hash();
523 1 : response_txs.pop_back();
524 1 : return query_response_factory_->createTransactionsPageResponse(
525 1 : std::move(response_txs), next_hash, total_size, query_hash_);
526 1 : }
527 :
528 12 : return query_response_factory_->createTransactionsPageResponse(
529 12 : std::move(response_txs), total_size, query_hash_);
530 16 : },
531 23 : notEnoughPermissionsResponse(perm_converter_, perms...));
532 23 : }
533 :
534 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
535 : const shared_model::interface::GetAccount &q) {
536 : using QueryTuple =
537 : QueryType<shared_model::interface::types::AccountIdType,
538 : shared_model::interface::types::DomainIdType,
539 : shared_model::interface::types::QuorumType,
540 : shared_model::interface::types::DetailType,
541 : std::string>;
542 : using PermissionTuple = boost::tuple<int>;
543 :
544 19 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
545 : t AS (
546 : SELECT a.account_id, a.domain_id, a.quorum, a.data, ARRAY_AGG(ar.role_id) AS roles
547 : FROM account AS a, account_has_roles AS ar
548 : WHERE a.account_id = :target_account_id
549 : AND ar.account_id = a.account_id
550 : GROUP BY a.account_id
551 : )
552 : SELECT account_id, domain_id, quorum, data, roles, perm
553 : FROM t RIGHT OUTER JOIN has_perms AS p ON TRUE
554 : )")
555 19 : % hasQueryPermission(creator_id_,
556 19 : q.accountId(),
557 : Role::kGetMyAccount,
558 : Role::kGetAllAccounts,
559 : Role::kGetDomainAccounts))
560 19 : .str();
561 :
562 : auto query_apply = [this](auto &account_id,
563 : auto &domain_id,
564 : auto &quorum,
565 : auto &data,
566 : auto &roles_str) {
567 10 : std::vector<shared_model::interface::types::RoleIdType> roles;
568 10 : auto roles_str_no_brackets = roles_str.substr(1, roles_str.size() - 2);
569 10 : boost::split(
570 : roles, roles_str_no_brackets, [](char c) { return c == ','; });
571 10 : return query_response_factory_->createAccountResponse(
572 10 : account_id, domain_id, quorum, data, std::move(roles), query_hash_);
573 10 : };
574 :
575 19 : return executeQuery<QueryTuple, PermissionTuple>(
576 : [&] {
577 19 : return (sql_.prepare << cmd,
578 19 : soci::use(q.accountId(), "target_account_id"));
579 0 : },
580 : [this, &q, &query_apply](auto range, auto &) {
581 11 : if (range.empty()) {
582 1 : return this->logAndReturnErrorResponse(
583 1 : QueryErrorType::kNoAccount, q.accountId(), 0);
584 : }
585 :
586 10 : return apply(range.front(), query_apply);
587 11 : },
588 19 : notEnoughPermissionsResponse(perm_converter_,
589 : Role::kGetMyAccount,
590 : Role::kGetAllAccounts,
591 : Role::kGetDomainAccounts));
592 19 : }
593 :
594 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
595 : const shared_model::interface::GetBlock &q) {
596 3 : if (not hasAccountRolePermission(Role::kGetBlocks, creator_id_)) {
597 : // no permission
598 1 : return query_response_factory_->createErrorQueryResponse(
599 : shared_model::interface::QueryResponseFactory::ErrorQueryType::
600 : kStatefulFailed,
601 1 : notEnoughPermissionsResponse(perm_converter_, Role::kGetBlocks)(),
602 : 2,
603 1 : query_hash_);
604 : }
605 :
606 2 : auto ledger_height = block_store_.last_id();
607 2 : if (q.height() > ledger_height) {
608 : // invalid height
609 1 : return logAndReturnErrorResponse(
610 : QueryErrorType::kStatefulFailed,
611 1 : "requested height (" + std::to_string(q.height())
612 1 : + ") is greater than the ledger's one ("
613 1 : + std::to_string(ledger_height) + ")",
614 : 3);
615 : }
616 :
617 : auto block_deserialization_msg = [height = q.height()] {
618 1 : return "could not retrieve block with given height: "
619 1 : + std::to_string(height);
620 0 : };
621 1 : auto serialized_block = block_store_.get(q.height());
622 1 : if (not serialized_block) {
623 : // for some reason, block with such height was not retrieved
624 0 : return logAndReturnErrorResponse(
625 0 : QueryErrorType::kStatefulFailed, block_deserialization_msg(), 1);
626 : }
627 :
628 1 : return converter_->deserialize(bytesToString(*serialized_block))
629 1 : .match(
630 : [this](iroha::expected::Value<
631 : std::unique_ptr<shared_model::interface::Block>> &block) {
632 1 : return this->query_response_factory_->createBlockResponse(
633 1 : std::move(block.value), query_hash_);
634 0 : },
635 : [this, err_msg = block_deserialization_msg()](const auto &err) {
636 : auto extended_error =
637 0 : err_msg + ", because it was not deserialized: " + err.error;
638 0 : return this->logAndReturnErrorResponse(
639 : QueryErrorType::kStatefulFailed,
640 0 : std::move(extended_error),
641 : 1);
642 0 : });
643 3 : }
644 :
645 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
646 : const shared_model::interface::GetSignatories &q) {
647 : using QueryTuple = QueryType<std::string>;
648 : using PermissionTuple = boost::tuple<int>;
649 :
650 20 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
651 : t AS (
652 : SELECT public_key FROM account_has_signatory
653 : WHERE account_id = :account_id
654 : )
655 : SELECT public_key, perm FROM t
656 : RIGHT OUTER JOIN has_perms ON TRUE
657 : )")
658 20 : % hasQueryPermission(creator_id_,
659 20 : q.accountId(),
660 : Role::kGetMySignatories,
661 : Role::kGetAllSignatories,
662 : Role::kGetDomainSignatories))
663 20 : .str();
664 :
665 20 : return executeQuery<QueryTuple, PermissionTuple>(
666 : [&] { return (sql_.prepare << cmd, soci::use(q.accountId())); },
667 : [this, &q](auto range, auto &) {
668 13 : if (range.empty()) {
669 1 : return this->logAndReturnErrorResponse(
670 1 : QueryErrorType::kNoSignatories, q.accountId(), 0);
671 : }
672 :
673 12 : auto pubkeys = boost::copy_range<
674 : std::vector<shared_model::interface::types::PubkeyType>>(
675 : range | boost::adaptors::transformed([](auto t) {
676 : return apply(t, [&](auto &public_key) {
677 44 : return shared_model::interface::types::PubkeyType{
678 44 : shared_model::crypto::Blob::fromHexString(public_key)};
679 0 : });
680 : }));
681 :
682 12 : return query_response_factory_->createSignatoriesResponse(
683 12 : pubkeys, query_hash_);
684 13 : },
685 20 : notEnoughPermissionsResponse(perm_converter_,
686 : Role::kGetMySignatories,
687 : Role::kGetAllSignatories,
688 : Role::kGetDomainSignatories));
689 20 : }
690 :
691 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
692 : const shared_model::interface::GetAccountTransactions &q) {
693 22 : std::string related_txs = R"(SELECT DISTINCT height, index
694 : FROM index_by_creator_height
695 : WHERE creator_id = :account_id
696 : ORDER BY height, index ASC)";
697 :
698 22 : const auto &pagination_info = q.paginationMeta();
699 22 : auto first_hash = pagination_info.firstTxHash();
700 : // retrieve one extra transaction to populate next_hash
701 22 : auto query_size = pagination_info.pageSize() + 1u;
702 :
703 : auto apply_query = [&](const auto &query) {
704 : return [&] {
705 22 : if (first_hash) {
706 22 : return (sql_.prepare << query,
707 22 : soci::use(q.accountId()),
708 2 : soci::use(first_hash->hex()),
709 22 : soci::use(query_size));
710 : } else {
711 20 : return (sql_.prepare << query,
712 20 : soci::use(q.accountId()),
713 20 : soci::use(query_size));
714 : }
715 22 : };
716 : };
717 :
718 : auto check_query = [this](const auto &q) {
719 2 : if (this->existsInDb<int>(
720 2 : "account", "account_id", "quorum", q.accountId())) {
721 1 : return QueryFallbackCheckResult{};
722 : }
723 1 : return QueryFallbackCheckResult{
724 1 : 5, "no account with such id found: " + q.accountId()};
725 2 : };
726 :
727 22 : return executeTransactionsQuery(q,
728 22 : std::move(check_query),
729 : related_txs,
730 22 : apply_query,
731 : Role::kGetMyAccTxs,
732 : Role::kGetAllAccTxs,
733 : Role::kGetDomainAccTxs);
734 22 : }
735 :
736 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
737 : const shared_model::interface::GetTransactions &q) {
738 : auto escape = [](auto &hash) { return "'" + hash.hex() + "'"; };
739 33 : std::string hash_str = std::accumulate(
740 33 : std::next(q.transactionHashes().begin()),
741 33 : q.transactionHashes().end(),
742 33 : escape(q.transactionHashes().front()),
743 : [&escape](auto &acc, auto &val) { return acc + "," + escape(val); });
744 :
745 : using QueryTuple =
746 : QueryType<shared_model::interface::types::HeightType, std::string>;
747 : using PermissionTuple = boost::tuple<int, int>;
748 :
749 : auto cmd =
750 34 : (boost::format(R"(WITH has_my_perm AS (%s),
751 : has_all_perm AS (%s),
752 : t AS (
753 : SELECT height, hash FROM position_by_hash WHERE hash IN (%s)
754 : )
755 : SELECT height, hash, has_my_perm.perm, has_all_perm.perm FROM t
756 : RIGHT OUTER JOIN has_my_perm ON TRUE
757 : RIGHT OUTER JOIN has_all_perm ON TRUE
758 33 : )") % getAccountRolePermissionCheckSql(Role::kGetMyTxs, "account_id")
759 34 : % getAccountRolePermissionCheckSql(Role::kGetAllTxs, "account_id")
760 33 : % hash_str)
761 33 : .str();
762 :
763 34 : return executeQuery<QueryTuple, PermissionTuple>(
764 : [&] {
765 34 : return (sql_.prepare << cmd, soci::use(creator_id_, "account_id"));
766 0 : },
767 : [&](auto range, auto &my_perm, auto &all_perm) {
768 34 : if (boost::size(range) != q.transactionHashes().size()) {
769 : // TODO [IR-1816] Akvinikym 03.12.18: replace magic number 4
770 : // with a named constant
771 : // at least one of the hashes in the query was invalid -
772 : // nonexistent or permissions were missed
773 2 : return this->logAndReturnErrorResponse(
774 : QueryErrorType::kStatefulFailed,
775 2 : "At least one of the supplied hashes is incorrect",
776 : 4);
777 : }
778 31 : std::map<uint64_t, std::unordered_set<std::string>> index;
779 : boost::for_each(range, [&index](auto t) {
780 : apply(t, [&index](auto &height, auto &hash) {
781 31 : index[height].insert(hash);
782 31 : });
783 31 : });
784 :
785 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
786 31 : response_txs;
787 62 : for (auto &block : index) {
788 20 : auto txs = this->getTransactionsFromBlock(
789 20 : block.first,
790 : [](auto size) {
791 31 : return boost::irange(static_cast<decltype(size)>(0), size);
792 : },
793 : [&](auto &tx) {
794 32 : return block.second.count(tx.hash().hex()) > 0
795 32 : and (all_perm
796 31 : or (my_perm
797 27 : and tx.creatorAccountId() == creator_id_));
798 : });
799 31 : std::move(
800 31 : txs.begin(), txs.end(), std::back_inserter(response_txs));
801 31 : }
802 :
803 31 : return query_response_factory_->createTransactionsResponse(
804 24 : std::move(response_txs), query_hash_);
805 33 : },
806 34 : notEnoughPermissionsResponse(
807 34 : perm_converter_, Role::kGetMyTxs, Role::kGetAllTxs));
808 34 : }
809 :
810 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
811 : const shared_model::interface::GetAccountAssetTransactions &q) {
812 23 : std::string related_txs = R"(SELECT DISTINCT height, index
813 : FROM position_by_account_asset
814 : WHERE account_id = :account_id
815 : AND asset_id = :asset_id
816 : ORDER BY height, index ASC)";
817 :
818 23 : const auto &pagination_info = q.paginationMeta();
819 23 : auto first_hash = pagination_info.firstTxHash();
820 : // retrieve one extra transaction to populate next_hash
821 23 : auto query_size = pagination_info.pageSize() + 1u;
822 :
823 : auto apply_query = [&](const auto &query) {
824 : return [&] {
825 23 : if (first_hash) {
826 23 : return (sql_.prepare << query,
827 23 : soci::use(q.accountId()),
828 2 : soci::use(q.assetId()),
829 2 : soci::use(first_hash->hex()),
830 23 : soci::use(query_size));
831 : } else {
832 21 : return (sql_.prepare << query,
833 21 : soci::use(q.accountId()),
834 21 : soci::use(q.assetId()),
835 21 : soci::use(query_size));
836 : }
837 23 : };
838 : };
839 :
840 : auto check_query = [this](const auto &q) {
841 3 : if (not this->existsInDb<int>(
842 3 : "account", "account_id", "quorum", q.accountId())) {
843 1 : return QueryFallbackCheckResult{
844 1 : 5, "no account with such id found: " + q.accountId()};
845 : }
846 2 : if (not this->existsInDb<int>(
847 2 : "asset", "asset_id", "precision", q.assetId())) {
848 1 : return QueryFallbackCheckResult{
849 1 : 6, "no asset with such id found: " + q.assetId()};
850 : }
851 :
852 1 : return QueryFallbackCheckResult{};
853 3 : };
854 :
855 23 : return executeTransactionsQuery(q,
856 23 : std::move(check_query),
857 : related_txs,
858 23 : apply_query,
859 : Role::kGetMyAccAstTxs,
860 : Role::kGetAllAccAstTxs,
861 : Role::kGetDomainAccAstTxs);
862 23 : }
863 :
864 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
865 : const shared_model::interface::GetAccountAssets &q) {
866 : using QueryTuple =
867 : QueryType<shared_model::interface::types::AccountIdType,
868 : shared_model::interface::types::AssetIdType,
869 : std::string>;
870 : using PermissionTuple = boost::tuple<int>;
871 :
872 19 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
873 : t AS (
874 : SELECT * FROM account_has_asset
875 : WHERE account_id = :account_id
876 : )
877 : SELECT account_id, asset_id, amount, perm FROM t
878 : RIGHT OUTER JOIN has_perms ON TRUE
879 : )")
880 19 : % hasQueryPermission(creator_id_,
881 19 : q.accountId(),
882 : Role::kGetMyAccAst,
883 : Role::kGetAllAccAst,
884 : Role::kGetDomainAccAst))
885 19 : .str();
886 :
887 19 : return executeQuery<QueryTuple, PermissionTuple>(
888 : [&] { return (sql_.prepare << cmd, soci::use(q.accountId())); },
889 : [&](auto range, auto &) {
890 : std::vector<
891 : std::tuple<shared_model::interface::types::AccountIdType,
892 : shared_model::interface::types::AssetIdType,
893 : shared_model::interface::Amount>>
894 12 : assets;
895 : boost::for_each(range, [&assets](auto t) {
896 17 : apply(t,
897 : [&assets](auto &account_id, auto &asset_id, auto &amount) {
898 17 : assets.push_back(std::make_tuple(
899 17 : std::move(account_id),
900 17 : std::move(asset_id),
901 17 : shared_model::interface::Amount(amount)));
902 17 : });
903 17 : });
904 12 : return query_response_factory_->createAccountAssetResponse(
905 12 : assets, query_hash_);
906 12 : },
907 19 : notEnoughPermissionsResponse(perm_converter_,
908 : Role::kGetMyAccAst,
909 : Role::kGetAllAccAst,
910 : Role::kGetDomainAccAst));
911 19 : }
912 :
913 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
914 : const shared_model::interface::GetAccountDetail &q) {
915 : using QueryTuple = QueryType<shared_model::interface::types::DetailType>;
916 : using PermissionTuple = boost::tuple<int>;
917 :
918 22 : std::string query_detail;
919 22 : if (q.key() and q.writer()) {
920 1 : auto filled_json = (boost::format("{\"%s\", \"%s\"}") % q.writer().get()
921 1 : % q.key().get());
922 1 : query_detail = (boost::format(R"(SELECT json_build_object('%s'::text,
923 : json_build_object('%s'::text, (SELECT data #>> '%s'
924 : FROM account WHERE account_id = :account_id))) AS json)")
925 1 : % q.writer().get() % q.key().get() % filled_json)
926 1 : .str();
927 21 : } else if (q.key() and not q.writer()) {
928 1 : query_detail =
929 1 : (boost::format(
930 : R"(SELECT json_object_agg(key, value) AS json FROM (SELECT
931 : json_build_object(kv.key, json_build_object('%1%'::text,
932 : kv.value -> '%1%')) FROM jsonb_each((SELECT data FROM account
933 : WHERE account_id = :account_id)) kv WHERE kv.value ? '%1%') AS
934 : jsons, json_each(json_build_object))")
935 1 : % q.key().get())
936 1 : .str();
937 20 : } else if (not q.key() and q.writer()) {
938 1 : query_detail = (boost::format(R"(SELECT json_build_object('%1%'::text,
939 : (SELECT data -> '%1%' FROM account WHERE account_id =
940 : :account_id)) AS json)")
941 1 : % q.writer().get())
942 1 : .str();
943 1 : } else {
944 19 : query_detail = (boost::format(R"(SELECT data#>>'{}' AS json FROM account
945 : WHERE account_id = :account_id)"))
946 19 : .str();
947 : }
948 22 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
949 : detail AS (%s)
950 : SELECT json, perm FROM detail
951 : RIGHT OUTER JOIN has_perms ON TRUE
952 : )")
953 22 : % hasQueryPermission(creator_id_,
954 22 : q.accountId(),
955 : Role::kGetMyAccDetail,
956 : Role::kGetAllAccDetail,
957 : Role::kGetDomainAccDetail)
958 22 : % query_detail)
959 22 : .str();
960 :
961 22 : return executeQuery<QueryTuple, PermissionTuple>(
962 : [&] {
963 22 : return (sql_.prepare << cmd,
964 22 : soci::use(q.accountId(), "account_id"));
965 0 : },
966 : [this, &q](auto range, auto &) {
967 15 : if (range.empty()) {
968 1 : return this->logAndReturnErrorResponse(
969 1 : QueryErrorType::kNoAccountDetail, q.accountId(), 0);
970 : }
971 :
972 : return apply(range.front(), [this](auto &json) {
973 14 : return query_response_factory_->createAccountDetailResponse(
974 14 : json, query_hash_);
975 0 : });
976 15 : },
977 22 : notEnoughPermissionsResponse(perm_converter_,
978 : Role::kGetMyAccDetail,
979 : Role::kGetAllAccDetail,
980 : Role::kGetDomainAccDetail));
981 22 : }
982 :
983 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
984 : const shared_model::interface::GetRoles &q) {
985 : using QueryTuple = QueryType<shared_model::interface::types::RoleIdType>;
986 : using PermissionTuple = boost::tuple<int>;
987 :
988 8 : auto cmd = (boost::format(
989 : R"(WITH has_perms AS (%s)
990 : SELECT role_id, perm FROM role
991 : RIGHT OUTER JOIN has_perms ON TRUE
992 8 : )") % getAccountRolePermissionCheckSql(Role::kGetRoles))
993 8 : .str();
994 :
995 8 : return executeQuery<QueryTuple, PermissionTuple>(
996 : [&] {
997 8 : return (sql_.prepare << cmd,
998 8 : soci::use(creator_id_, "role_account_id"));
999 0 : },
1000 : [&](auto range, auto &) {
1001 6 : auto roles = boost::copy_range<
1002 : std::vector<shared_model::interface::types::RoleIdType>>(
1003 : range | boost::adaptors::transformed([](auto t) {
1004 : return apply(t, [](auto &role_id) { return role_id; });
1005 : }));
1006 :
1007 6 : return query_response_factory_->createRolesResponse(roles,
1008 6 : query_hash_);
1009 6 : },
1010 8 : notEnoughPermissionsResponse(perm_converter_, Role::kGetRoles));
1011 8 : }
1012 :
1013 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
1014 : const shared_model::interface::GetRolePermissions &q) {
1015 : using QueryTuple = QueryType<std::string>;
1016 : using PermissionTuple = boost::tuple<int>;
1017 :
1018 5 : auto cmd = (boost::format(
1019 : R"(WITH has_perms AS (%s),
1020 : perms AS (SELECT permission FROM role_has_permissions
1021 : WHERE role_id = :role_name)
1022 : SELECT permission, perm FROM perms
1023 : RIGHT OUTER JOIN has_perms ON TRUE
1024 5 : )") % getAccountRolePermissionCheckSql(Role::kGetRoles))
1025 5 : .str();
1026 :
1027 5 : return executeQuery<QueryTuple, PermissionTuple>(
1028 : [&] {
1029 5 : return (sql_.prepare << cmd,
1030 5 : soci::use(creator_id_, "role_account_id"),
1031 5 : soci::use(q.roleId(), "role_name"));
1032 0 : },
1033 : [this, &q](auto range, auto &) {
1034 3 : if (range.empty()) {
1035 1 : return this->logAndReturnErrorResponse(
1036 : QueryErrorType::kNoRoles,
1037 1 : "{" + q.roleId() + ", " + creator_id_ + "}",
1038 : 0);
1039 : }
1040 :
1041 : return apply(range.front(), [this](auto &permission) {
1042 2 : return query_response_factory_->createRolePermissionsResponse(
1043 2 : shared_model::interface::RolePermissionSet(permission),
1044 2 : query_hash_);
1045 : });
1046 3 : },
1047 5 : notEnoughPermissionsResponse(perm_converter_, Role::kGetRoles));
1048 5 : }
1049 :
1050 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
1051 : const shared_model::interface::GetAssetInfo &q) {
1052 : using QueryTuple =
1053 : QueryType<shared_model::interface::types::DomainIdType, uint32_t>;
1054 : using PermissionTuple = boost::tuple<int>;
1055 :
1056 5 : auto cmd = (boost::format(
1057 : R"(WITH has_perms AS (%s),
1058 : perms AS (SELECT domain_id, precision FROM asset
1059 : WHERE asset_id = :asset_id)
1060 : SELECT domain_id, precision, perm FROM perms
1061 : RIGHT OUTER JOIN has_perms ON TRUE
1062 5 : )") % getAccountRolePermissionCheckSql(Role::kReadAssets))
1063 5 : .str();
1064 :
1065 5 : return executeQuery<QueryTuple, PermissionTuple>(
1066 : [&] {
1067 5 : return (sql_.prepare << cmd,
1068 5 : soci::use(creator_id_, "role_account_id"),
1069 5 : soci::use(q.assetId(), "asset_id"));
1070 0 : },
1071 : [this, &q](auto range, auto &) {
1072 3 : if (range.empty()) {
1073 2 : return this->logAndReturnErrorResponse(
1074 : QueryErrorType::kNoAsset,
1075 2 : "{" + q.assetId() + ", " + creator_id_ + "}",
1076 : 0);
1077 : }
1078 :
1079 1 : return apply(range.front(),
1080 : [this, &q](auto &domain_id, auto &precision) {
1081 1 : return query_response_factory_->createAssetResponse(
1082 1 : q.assetId(), domain_id, precision, query_hash_);
1083 0 : });
1084 3 : },
1085 5 : notEnoughPermissionsResponse(perm_converter_, Role::kReadAssets));
1086 5 : }
1087 :
1088 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
1089 : const shared_model::interface::GetPendingTransactions &q) {
1090 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
1091 7 : response_txs;
1092 : auto interface_txs =
1093 7 : pending_txs_storage_->getPendingTransactions(creator_id_);
1094 7 : response_txs.reserve(interface_txs.size());
1095 :
1096 7 : std::transform(interface_txs.begin(),
1097 7 : interface_txs.end(),
1098 7 : std::back_inserter(response_txs),
1099 : [](auto &tx) { return clone(*tx); });
1100 7 : return query_response_factory_->createTransactionsResponse(
1101 7 : std::move(response_txs), query_hash_);
1102 7 : }
1103 :
1104 : template <typename ReturnValueType>
1105 : bool PostgresQueryExecutorVisitor::existsInDb(
1106 : const std::string &table_name,
1107 : const std::string &key_name,
1108 : const std::string &value_name,
1109 : const std::string &value) const {
1110 7 : auto cmd = (boost::format(R"(SELECT %s
1111 : FROM %s
1112 : WHERE %s = '%s'
1113 : LIMIT 1)")
1114 7 : % value_name % table_name % key_name % value)
1115 7 : .str();
1116 7 : soci::rowset<ReturnValueType> result = this->sql_.prepare << cmd;
1117 7 : return result.begin() != result.end();
1118 7 : }
1119 :
1120 : } // namespace ametsuchi
1121 : } // namespace iroha
|