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_pending_transactions.hpp"
30 : #include "interfaces/queries/get_role_permissions.hpp"
31 : #include "interfaces/queries/get_roles.hpp"
32 : #include "interfaces/queries/get_signatories.hpp"
33 : #include "interfaces/queries/get_transactions.hpp"
34 : #include "interfaces/queries/query.hpp"
35 :
36 : using namespace shared_model::interface::permissions;
37 :
38 : namespace {
39 :
40 : using namespace iroha;
41 :
42 : shared_model::interface::types::DomainIdType getDomainFromName(
43 : const shared_model::interface::types::AccountIdType &account_id) {
44 : // TODO 03.10.18 andrei: IR-1728 Move getDomainFromName to shared_model
45 224 : std::vector<std::string> res;
46 224 : boost::split(res, account_id, boost::is_any_of("@"));
47 224 : return res.at(1);
48 224 : }
49 :
50 : std::string checkAccountRolePermission(
51 : shared_model::interface::permissions::Role permission,
52 : const std::string &account_alias = "role_account_id") {
53 : const auto perm_str =
54 85 : shared_model::interface::RolePermissionSet({permission}).toBitstring();
55 85 : const auto bits = shared_model::interface::RolePermissionSet::size();
56 : // TODO 14.09.18 andrei: IR-1708 Load SQL from separate files
57 88 : std::string query = (boost::format(R"(
58 : SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
59 : & '%2%') = '%2%' AS perm FROM role_has_permissions AS rp
60 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
61 : WHERE ar.account_id = :%3%)")
62 86 : % bits % perm_str % account_alias)
63 87 : .str();
64 88 : return query;
65 88 : }
66 :
67 : /**
68 : * Generate an SQL subquery which checks if creator has corresponding
69 : * permissions for target account
70 : * It verifies individual, domain, and global permissions, and returns true if
71 : * any of listed permissions is present
72 : */
73 : auto hasQueryPermission(
74 : const shared_model::interface::types::AccountIdType &creator,
75 : const shared_model::interface::types::AccountIdType &target_account,
76 : Role indiv_permission_id,
77 : Role all_permission_id,
78 : Role domain_permission_id) {
79 112 : const auto bits = shared_model::interface::RolePermissionSet::size();
80 : const auto perm_str =
81 112 : shared_model::interface::RolePermissionSet({indiv_permission_id})
82 : .toBitstring();
83 : const auto all_perm_str =
84 112 : shared_model::interface::RolePermissionSet({all_permission_id})
85 112 : .toBitstring();
86 : const auto domain_perm_str =
87 112 : shared_model::interface::RolePermissionSet({domain_permission_id})
88 112 : .toBitstring();
89 :
90 112 : boost::format cmd(R"(
91 : WITH
92 : has_indiv_perm AS (
93 : SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
94 : & '%3%') = '%3%' FROM role_has_permissions AS rp
95 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
96 : WHERE ar.account_id = '%2%'
97 : ),
98 : has_all_perm AS (
99 : SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
100 : & '%4%') = '%4%' FROM role_has_permissions AS rp
101 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
102 : WHERE ar.account_id = '%2%'
103 : ),
104 : has_domain_perm AS (
105 : SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
106 : & '%5%') = '%5%' FROM role_has_permissions AS rp
107 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
108 : WHERE ar.account_id = '%2%'
109 : )
110 : SELECT ('%2%' = '%6%' AND (SELECT * FROM has_indiv_perm))
111 : OR (SELECT * FROM has_all_perm)
112 : OR ('%7%' = '%8%' AND (SELECT * FROM has_domain_perm)) AS perm
113 : )");
114 :
115 112 : return (cmd % bits % creator % perm_str % all_perm_str % domain_perm_str
116 112 : % target_account % getDomainFromName(creator)
117 112 : % getDomainFromName(target_account))
118 112 : .str();
119 112 : }
120 :
121 : /// Query result is a tuple of optionals, since there could be no entry
122 : template <typename... Value>
123 : using QueryType = boost::tuple<boost::optional<Value>...>;
124 :
125 : /**
126 : * Create an error response in case user does not have permissions to perform
127 : * a query
128 : * @tparam Roles - type of roles
129 : * @param roles, which user lacks
130 : * @return lambda returning the error response itself
131 : */
132 : template <typename... Roles>
133 : auto notEnoughPermissionsResponse(
134 : std::shared_ptr<shared_model::interface::PermissionToString>
135 : perm_converter,
136 : Roles... roles) {
137 : return [perm_converter, roles...] {
138 43 : std::string error = "user must have at least one of the permissions: ";
139 172 : for (auto role : {roles...}) {
140 129 : error += perm_converter->toString(role) + ", ";
141 : }
142 43 : return error;
143 43 : };
144 : }
145 :
146 : } // namespace
147 :
148 : namespace iroha {
149 : namespace ametsuchi {
150 :
151 : template <typename RangeGen, typename Pred>
152 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
153 : PostgresQueryExecutorVisitor::getTransactionsFromBlock(uint64_t block_id,
154 : RangeGen &&range_gen,
155 : Pred &&pred) {
156 30 : std::vector<std::unique_ptr<shared_model::interface::Transaction>> result;
157 30 : auto serialized_block = block_store_.get(block_id);
158 33 : if (not serialized_block) {
159 0 : log_->error("Failed to retrieve block with id {}", block_id);
160 0 : return result;
161 : }
162 : auto deserialized_block =
163 33 : converter_->deserialize(bytesToString(*serialized_block));
164 : // boost::get of pointer returns pointer to requested type, or nullptr
165 33 : if (auto e =
166 33 : boost::get<expected::Error<std::string>>(&deserialized_block)) {
167 0 : log_->error(e->error);
168 0 : return result;
169 : }
170 :
171 33 : auto &block =
172 33 : boost::get<
173 : expected::Value<std::unique_ptr<shared_model::interface::Block>>>(
174 33 : deserialized_block)
175 33 : .value;
176 :
177 33 : boost::transform(range_gen(boost::size(block->transactions()))
178 33 : | boost::adaptors::transformed(
179 : [&block](auto i) -> decltype(auto) {
180 69 : return block->transactions()[i];
181 0 : })
182 33 : | boost::adaptors::filtered(pred),
183 33 : std::back_inserter(result),
184 : [&](const auto &tx) { return clone(tx); });
185 :
186 32 : return result;
187 33 : }
188 :
189 : template <typename QueryTuple,
190 : typename PermissionTuple,
191 : typename QueryExecutor,
192 : typename ResponseCreator,
193 : typename ErrResponse>
194 : QueryExecutorResult PostgresQueryExecutorVisitor::executeQuery(
195 : QueryExecutor &&query_executor,
196 : ResponseCreator &&response_creator,
197 : ErrResponse &&err_response) {
198 : using T = concat<QueryTuple, PermissionTuple>;
199 : try {
200 34 : soci::rowset<T> st = std::forward<QueryExecutor>(query_executor)();
201 34 : auto range = boost::make_iterator_range(st.begin(), st.end());
202 :
203 28 : return apply(
204 34 : viewPermissions<PermissionTuple>(range.front()),
205 : [this, range, &response_creator, &err_response](auto... perms) {
206 34 : bool temp[] = {not perms...};
207 : if (std::all_of(std::begin(temp), std::end(temp), [](auto b) {
208 37 : return b;
209 : })) {
210 8 : return this->logAndReturnErrorResponse(
211 : QueryErrorType::kStatefulFailed,
212 8 : std::forward<ErrResponse>(err_response)());
213 : }
214 33 : auto query_range = range
215 : | boost::adaptors::transformed([](auto &t) {
216 89 : return rebind(viewQuery<QueryTuple>(t));
217 0 : })
218 : | boost::adaptors::filtered([](const auto &t) {
219 45 : return static_cast<bool>(t);
220 : })
221 : | boost::adaptors::transformed([](auto t) { return *t; });
222 33 : return std::forward<ResponseCreator>(response_creator)(
223 33 : query_range, perms...);
224 34 : });
225 34 : } catch (const std::exception &e) {
226 0 : return logAndReturnErrorResponse(QueryErrorType::kStatefulFailed,
227 0 : e.what());
228 0 : }
229 34 : }
230 :
231 : PostgresQueryExecutor::PostgresQueryExecutor(
232 : std::unique_ptr<soci::session> sql,
233 : KeyValueStorage &block_store,
234 : std::shared_ptr<PendingTransactionStorage> pending_txs_storage,
235 : std::shared_ptr<shared_model::interface::BlockJsonConverter> converter,
236 : std::shared_ptr<shared_model::interface::QueryResponseFactory>
237 : response_factory,
238 : std::shared_ptr<shared_model::interface::PermissionToString>
239 : perm_converter)
240 171 : : sql_(std::move(sql)),
241 171 : block_store_(block_store),
242 171 : pending_txs_storage_(std::move(pending_txs_storage)),
243 171 : visitor_(*sql_,
244 170 : block_store_,
245 170 : pending_txs_storage_,
246 170 : std::move(converter),
247 170 : response_factory,
248 170 : perm_converter),
249 171 : query_response_factory_{std::move(response_factory)},
250 171 : log_(logger::log("PostgresQueryExecutor")) {}
251 :
252 : QueryExecutorResult PostgresQueryExecutor::validateAndExecute(
253 : const shared_model::interface::Query &query) {
254 169 : visitor_.setCreatorId(query.creatorAccountId());
255 169 : visitor_.setQueryHash(query.hash());
256 169 : return boost::apply_visitor(visitor_, query.get());
257 : }
258 :
259 : bool PostgresQueryExecutor::validate(
260 : const shared_model::interface::BlocksQuery &query) {
261 : using T = boost::tuple<int>;
262 2 : boost::format cmd(R"(%s)");
263 : try {
264 2 : soci::rowset<T> st =
265 2 : (sql_->prepare
266 2 : << (cmd % checkAccountRolePermission(Role::kGetBlocks)).str(),
267 2 : soci::use(query.creatorAccountId(), "role_account_id"));
268 :
269 2 : return st.begin()->get<0>();
270 2 : } catch (const std::exception &e) {
271 0 : log_->error("Failed to validate query: {}", e.what());
272 0 : return false;
273 0 : }
274 2 : }
275 :
276 : PostgresQueryExecutorVisitor::PostgresQueryExecutorVisitor(
277 : soci::session &sql,
278 : KeyValueStorage &block_store,
279 : std::shared_ptr<PendingTransactionStorage> pending_txs_storage,
280 : std::shared_ptr<shared_model::interface::BlockJsonConverter> converter,
281 : std::shared_ptr<shared_model::interface::QueryResponseFactory>
282 : response_factory,
283 : std::shared_ptr<shared_model::interface::PermissionToString>
284 : perm_converter)
285 170 : : sql_(sql),
286 170 : block_store_(block_store),
287 171 : pending_txs_storage_(std::move(pending_txs_storage)),
288 171 : converter_(std::move(converter)),
289 171 : query_response_factory_{std::move(response_factory)},
290 171 : perm_converter_(std::move(perm_converter)),
291 171 : log_(logger::log("PostgresQueryExecutorVisitor")) {}
292 :
293 : void PostgresQueryExecutorVisitor::setCreatorId(
294 : const shared_model::interface::types::AccountIdType &creator_id) {
295 169 : creator_id_ = creator_id;
296 169 : }
297 :
298 : void PostgresQueryExecutorVisitor::setQueryHash(
299 : const shared_model::interface::types::HashType &query_hash) {
300 169 : query_hash_ = query_hash;
301 169 : }
302 :
303 : std::unique_ptr<shared_model::interface::QueryResponse>
304 : PostgresQueryExecutorVisitor::logAndReturnErrorResponse(
305 : iroha::ametsuchi::QueryErrorType error_type,
306 : std::string error_body) const {
307 : using QueryErrorType = iroha::ametsuchi::QueryErrorType;
308 :
309 : auto make_error_response = [this, error_type](std::string error) {
310 56 : return query_response_factory_->createErrorQueryResponse(
311 56 : error_type, error, query_hash_);
312 0 : };
313 :
314 56 : std::string error;
315 56 : switch (error_type) {
316 : case QueryErrorType::kNoAccount:
317 1 : error = "could find account with such id: " + error_body;
318 1 : break;
319 : case QueryErrorType::kNoSignatories:
320 1 : error = "no signatories found in account with such id: " + error_body;
321 1 : break;
322 : case QueryErrorType::kNoAccountDetail:
323 1 : error = "no details in account with such id: " + error_body;
324 1 : break;
325 : case QueryErrorType::kNoRoles:
326 1 : error =
327 1 : "no role with such name in account with such id: " + error_body;
328 1 : break;
329 : case QueryErrorType::kNoAsset:
330 2 : error =
331 2 : "no asset with such name in account with such id: " + error_body;
332 2 : break;
333 : // other error are either handled by generic response or do not appear
334 : // yet
335 : default:
336 50 : error = "failed to execute query: " + error_body;
337 50 : break;
338 : }
339 :
340 56 : log_->error(error);
341 56 : return make_error_response(error);
342 56 : }
343 :
344 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
345 : const shared_model::interface::GetAccount &q) {
346 : using QueryTuple =
347 : QueryType<shared_model::interface::types::AccountIdType,
348 : shared_model::interface::types::DomainIdType,
349 : shared_model::interface::types::QuorumType,
350 : shared_model::interface::types::DetailType,
351 : std::string>;
352 : using PermissionTuple = boost::tuple<int>;
353 :
354 19 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
355 : t AS (
356 : SELECT a.account_id, a.domain_id, a.quorum, a.data, ARRAY_AGG(ar.role_id) AS roles
357 : FROM account AS a, account_has_roles AS ar
358 : WHERE a.account_id = :target_account_id
359 : AND ar.account_id = a.account_id
360 : GROUP BY a.account_id
361 : )
362 : SELECT account_id, domain_id, quorum, data, roles, perm
363 : FROM t RIGHT OUTER JOIN has_perms AS p ON TRUE
364 : )")
365 19 : % hasQueryPermission(creator_id_,
366 19 : q.accountId(),
367 : Role::kGetMyAccount,
368 : Role::kGetAllAccounts,
369 : Role::kGetDomainAccounts))
370 19 : .str();
371 :
372 : auto query_apply = [this](auto &account_id,
373 : auto &domain_id,
374 : auto &quorum,
375 : auto &data,
376 : auto &roles_str) {
377 10 : std::vector<shared_model::interface::types::RoleIdType> roles;
378 10 : auto roles_str_no_brackets = roles_str.substr(1, roles_str.size() - 2);
379 10 : boost::split(
380 : roles, roles_str_no_brackets, [](char c) { return c == ','; });
381 10 : return query_response_factory_->createAccountResponse(
382 10 : account_id, domain_id, quorum, data, std::move(roles), query_hash_);
383 10 : };
384 :
385 19 : return executeQuery<QueryTuple, PermissionTuple>(
386 : [&] {
387 19 : return (sql_.prepare << cmd,
388 19 : soci::use(q.accountId(), "target_account_id"));
389 0 : },
390 : [this, &q, &query_apply](auto range, auto &) {
391 11 : if (range.empty()) {
392 1 : return this->logAndReturnErrorResponse(QueryErrorType::kNoAccount,
393 1 : q.accountId());
394 : }
395 :
396 10 : return apply(range.front(), query_apply);
397 11 : },
398 19 : notEnoughPermissionsResponse(perm_converter_,
399 : Role::kGetMyAccount,
400 : Role::kGetAllAccounts,
401 : Role::kGetDomainAccounts));
402 19 : }
403 :
404 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
405 : const shared_model::interface::GetSignatories &q) {
406 : using QueryTuple = QueryType<std::string>;
407 : using PermissionTuple = boost::tuple<int>;
408 :
409 20 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
410 : t AS (
411 : SELECT public_key FROM account_has_signatory
412 : WHERE account_id = :account_id
413 : )
414 : SELECT public_key, perm FROM t
415 : RIGHT OUTER JOIN has_perms ON TRUE
416 : )")
417 20 : % hasQueryPermission(creator_id_,
418 20 : q.accountId(),
419 : Role::kGetMySignatories,
420 : Role::kGetAllSignatories,
421 : Role::kGetDomainSignatories))
422 20 : .str();
423 :
424 20 : return executeQuery<QueryTuple, PermissionTuple>(
425 : [&] { return (sql_.prepare << cmd, soci::use(q.accountId())); },
426 : [this, &q](auto range, auto &) {
427 13 : if (range.empty()) {
428 1 : return this->logAndReturnErrorResponse(
429 1 : QueryErrorType::kNoSignatories, q.accountId());
430 : }
431 :
432 12 : auto pubkeys = boost::copy_range<
433 : std::vector<shared_model::interface::types::PubkeyType>>(
434 : range | boost::adaptors::transformed([](auto t) {
435 : return apply(t, [&](auto &public_key) {
436 44 : return shared_model::interface::types::PubkeyType{
437 44 : shared_model::crypto::Blob::fromHexString(public_key)};
438 0 : });
439 : }));
440 :
441 12 : return query_response_factory_->createSignatoriesResponse(
442 12 : pubkeys, query_hash_);
443 13 : },
444 20 : notEnoughPermissionsResponse(perm_converter_,
445 : Role::kGetMySignatories,
446 : Role::kGetAllSignatories,
447 : Role::kGetDomainSignatories));
448 20 : }
449 :
450 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
451 : const shared_model::interface::GetAccountTransactions &q) {
452 : using QueryTuple =
453 : QueryType<shared_model::interface::types::HeightType, uint64_t>;
454 : using PermissionTuple = boost::tuple<int>;
455 :
456 17 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
457 : t AS (
458 : SELECT DISTINCT has.height, index
459 : FROM height_by_account_set AS has
460 : JOIN index_by_creator_height AS ich ON has.height = ich.height
461 : AND has.account_id = ich.creator_id
462 : WHERE account_id = :account_id
463 : ORDER BY has.height, index ASC
464 : )
465 : SELECT height, index, perm FROM t
466 : RIGHT OUTER JOIN has_perms ON TRUE
467 : )")
468 17 : % hasQueryPermission(creator_id_,
469 17 : q.accountId(),
470 : Role::kGetMyAccTxs,
471 : Role::kGetAllAccTxs,
472 : Role::kGetDomainAccTxs))
473 17 : .str();
474 :
475 17 : return executeQuery<QueryTuple, PermissionTuple>(
476 : [&] { return (sql_.prepare << cmd, soci::use(q.accountId())); },
477 : [&](auto range, auto &) {
478 9 : std::map<uint64_t, std::vector<uint64_t>> index;
479 : boost::for_each(range, [&index](auto t) {
480 : apply(t, [&index](auto &height, auto &idx) {
481 25 : index[height].push_back(idx);
482 25 : });
483 25 : });
484 :
485 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
486 9 : response_txs;
487 33 : for (auto &block : index) {
488 24 : auto txs = this->getTransactionsFromBlock(
489 24 : block.first,
490 : [&block](auto) { return block.second; },
491 : [](auto &) { return true; });
492 24 : std::move(
493 24 : txs.begin(), txs.end(), std::back_inserter(response_txs));
494 24 : }
495 :
496 9 : return query_response_factory_->createTransactionsResponse(
497 9 : std::move(response_txs), query_hash_);
498 9 : },
499 17 : notEnoughPermissionsResponse(perm_converter_,
500 : Role::kGetMyAccTxs,
501 : Role::kGetAllAccTxs,
502 : Role::kGetDomainAccTxs));
503 17 : }
504 :
505 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
506 : const shared_model::interface::GetTransactions &q) {
507 : auto escape = [](auto &hash) { return "'" + hash.hex() + "'"; };
508 33 : std::string hash_str = std::accumulate(
509 33 : std::next(q.transactionHashes().begin()),
510 33 : q.transactionHashes().end(),
511 33 : escape(q.transactionHashes().front()),
512 : [&escape](auto &acc, auto &val) { return acc + "," + escape(val); });
513 :
514 : using QueryTuple =
515 : QueryType<shared_model::interface::types::HeightType, std::string>;
516 : using PermissionTuple = boost::tuple<int, int>;
517 :
518 34 : auto cmd = (boost::format(R"(WITH has_my_perm AS (%s),
519 : has_all_perm AS (%s),
520 : t AS (
521 : SELECT height, hash FROM height_by_hash WHERE hash IN (%s)
522 : )
523 : SELECT height, hash, has_my_perm.perm, has_all_perm.perm FROM t
524 : RIGHT OUTER JOIN has_my_perm ON TRUE
525 : RIGHT OUTER JOIN has_all_perm ON TRUE
526 34 : )") % checkAccountRolePermission(Role::kGetMyTxs, "account_id")
527 34 : % checkAccountRolePermission(Role::kGetAllTxs, "account_id")
528 34 : % hash_str)
529 34 : .str();
530 :
531 34 : return executeQuery<QueryTuple, PermissionTuple>(
532 : [&] {
533 34 : return (sql_.prepare << cmd, soci::use(creator_id_, "account_id"));
534 0 : },
535 : [&](auto range, auto &my_perm, auto &all_perm) {
536 32 : std::map<uint64_t, std::unordered_set<std::string>> index;
537 : boost::for_each(range, [&index](auto t) {
538 : apply(t, [&index](auto &height, auto &hash) {
539 34 : index[height].insert(hash);
540 34 : });
541 34 : });
542 :
543 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
544 33 : response_txs;
545 66 : for (auto &block : index) {
546 26 : auto txs = this->getTransactionsFromBlock(
547 26 : block.first,
548 : [](auto size) {
549 33 : return boost::irange(static_cast<decltype(size)>(0), size);
550 : },
551 : [&](auto &tx) {
552 37 : return block.second.count(tx.hash().hex()) > 0
553 37 : and (all_perm
554 34 : or (my_perm
555 30 : and tx.creatorAccountId() == creator_id_));
556 : });
557 33 : std::move(
558 33 : txs.begin(), txs.end(), std::back_inserter(response_txs));
559 33 : }
560 :
561 33 : return query_response_factory_->createTransactionsResponse(
562 33 : std::move(response_txs), query_hash_);
563 33 : },
564 34 : notEnoughPermissionsResponse(
565 34 : perm_converter_, Role::kGetMyTxs, Role::kGetAllTxs));
566 34 : }
567 :
568 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
569 : const shared_model::interface::GetAccountAssetTransactions &q) {
570 : using QueryTuple =
571 : QueryType<shared_model::interface::types::HeightType, uint64_t>;
572 : using PermissionTuple = boost::tuple<int>;
573 :
574 15 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
575 : t AS (
576 : SELECT DISTINCT has.height, index
577 : FROM height_by_account_set AS has
578 : JOIN index_by_id_height_asset AS ich ON has.height = ich.height
579 : AND has.account_id = ich.id
580 : WHERE account_id = :account_id
581 : AND asset_id = :asset_id
582 : ORDER BY has.height, index ASC
583 : )
584 : SELECT height, index, perm FROM t
585 : RIGHT OUTER JOIN has_perms ON TRUE
586 : )")
587 15 : % hasQueryPermission(creator_id_,
588 15 : q.accountId(),
589 : Role::kGetMyAccAstTxs,
590 : Role::kGetAllAccAstTxs,
591 : Role::kGetDomainAccAstTxs))
592 15 : .str();
593 :
594 15 : return executeQuery<QueryTuple, PermissionTuple>(
595 : [&] {
596 15 : return (sql_.prepare << cmd,
597 15 : soci::use(q.accountId(), "account_id"),
598 15 : soci::use(q.assetId(), "asset_id"));
599 0 : },
600 : [&](auto range, auto &) {
601 9 : std::map<uint64_t, std::vector<uint64_t>> index;
602 : boost::for_each(range, [&index](auto t) {
603 : apply(t, [&index](auto &height, auto &idx) {
604 18 : index[height].push_back(idx);
605 18 : });
606 18 : });
607 :
608 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
609 9 : response_txs;
610 27 : for (auto &block : index) {
611 18 : auto txs = this->getTransactionsFromBlock(
612 18 : block.first,
613 : [&block](auto) { return block.second; },
614 : [](auto &) { return true; });
615 18 : std::move(
616 18 : txs.begin(), txs.end(), std::back_inserter(response_txs));
617 18 : }
618 :
619 9 : return query_response_factory_->createTransactionsResponse(
620 9 : std::move(response_txs), query_hash_);
621 9 : },
622 15 : notEnoughPermissionsResponse(perm_converter_,
623 : Role::kGetMyAccAstTxs,
624 : Role::kGetAllAccAstTxs,
625 : Role::kGetDomainAccAstTxs));
626 15 : }
627 :
628 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
629 : const shared_model::interface::GetAccountAssets &q) {
630 : using QueryTuple =
631 : QueryType<shared_model::interface::types::AccountIdType,
632 : shared_model::interface::types::AssetIdType,
633 : std::string>;
634 : using PermissionTuple = boost::tuple<int>;
635 :
636 19 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
637 : t AS (
638 : SELECT * FROM account_has_asset
639 : WHERE account_id = :account_id
640 : )
641 : SELECT account_id, asset_id, amount, perm FROM t
642 : RIGHT OUTER JOIN has_perms ON TRUE
643 : )")
644 19 : % hasQueryPermission(creator_id_,
645 19 : q.accountId(),
646 : Role::kGetMyAccAst,
647 : Role::kGetAllAccAst,
648 : Role::kGetDomainAccAst))
649 19 : .str();
650 :
651 19 : return executeQuery<QueryTuple, PermissionTuple>(
652 : [&] { return (sql_.prepare << cmd, soci::use(q.accountId())); },
653 : [&](auto range, auto &) {
654 : std::vector<
655 : std::tuple<shared_model::interface::types::AccountIdType,
656 : shared_model::interface::types::AssetIdType,
657 : shared_model::interface::Amount>>
658 12 : assets;
659 : boost::for_each(range, [&assets](auto t) {
660 17 : apply(t,
661 : [&assets](auto &account_id, auto &asset_id, auto &amount) {
662 17 : assets.push_back(std::make_tuple(
663 17 : std::move(account_id),
664 17 : std::move(asset_id),
665 17 : shared_model::interface::Amount(amount)));
666 17 : });
667 17 : });
668 12 : return query_response_factory_->createAccountAssetResponse(
669 12 : assets, query_hash_);
670 12 : },
671 19 : notEnoughPermissionsResponse(perm_converter_,
672 : Role::kGetMyAccAst,
673 : Role::kGetAllAccAst,
674 : Role::kGetDomainAccAst));
675 19 : }
676 :
677 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
678 : const shared_model::interface::GetAccountDetail &q) {
679 : using QueryTuple = QueryType<shared_model::interface::types::DetailType>;
680 : using PermissionTuple = boost::tuple<int>;
681 :
682 22 : std::string query_detail;
683 22 : if (q.key() and q.writer()) {
684 1 : auto filled_json = (boost::format("{\"%s\", \"%s\"}") % q.writer().get()
685 1 : % q.key().get());
686 1 : query_detail = (boost::format(R"(SELECT json_build_object('%s'::text,
687 : json_build_object('%s'::text, (SELECT data #>> '%s'
688 : FROM account WHERE account_id = :account_id))) AS json)")
689 1 : % q.writer().get() % q.key().get() % filled_json)
690 1 : .str();
691 21 : } else if (q.key() and not q.writer()) {
692 1 : query_detail =
693 1 : (boost::format(
694 : R"(SELECT json_object_agg(key, value) AS json FROM (SELECT
695 : json_build_object(kv.key, json_build_object('%1%'::text,
696 : kv.value -> '%1%')) FROM jsonb_each((SELECT data FROM account
697 : WHERE account_id = :account_id)) kv WHERE kv.value ? '%1%') AS
698 : jsons, json_each(json_build_object))")
699 1 : % q.key().get())
700 1 : .str();
701 20 : } else if (not q.key() and q.writer()) {
702 1 : query_detail = (boost::format(R"(SELECT json_build_object('%1%'::text,
703 : (SELECT data -> '%1%' FROM account WHERE account_id =
704 : :account_id)) AS json)")
705 1 : % q.writer().get())
706 1 : .str();
707 1 : } else {
708 19 : query_detail = (boost::format(R"(SELECT data#>>'{}' AS json FROM account
709 : WHERE account_id = :account_id)"))
710 19 : .str();
711 : }
712 22 : auto cmd = (boost::format(R"(WITH has_perms AS (%s),
713 : detail AS (%s)
714 : SELECT json, perm FROM detail
715 : RIGHT OUTER JOIN has_perms ON TRUE
716 : )")
717 22 : % hasQueryPermission(creator_id_,
718 22 : q.accountId(),
719 : Role::kGetMyAccDetail,
720 : Role::kGetAllAccDetail,
721 : Role::kGetDomainAccDetail)
722 22 : % query_detail)
723 22 : .str();
724 :
725 22 : return executeQuery<QueryTuple, PermissionTuple>(
726 : [&] {
727 22 : return (sql_.prepare << cmd,
728 22 : soci::use(q.accountId(), "account_id"));
729 0 : },
730 : [this, &q](auto range, auto &) {
731 15 : if (range.empty()) {
732 1 : return this->logAndReturnErrorResponse(
733 1 : QueryErrorType::kNoAccountDetail, q.accountId());
734 : }
735 :
736 : return apply(range.front(), [this](auto &json) {
737 14 : return query_response_factory_->createAccountDetailResponse(
738 14 : json, query_hash_);
739 0 : });
740 15 : },
741 22 : notEnoughPermissionsResponse(perm_converter_,
742 : Role::kGetMyAccDetail,
743 : Role::kGetAllAccDetail,
744 : Role::kGetDomainAccDetail));
745 22 : }
746 :
747 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
748 : const shared_model::interface::GetRoles &q) {
749 : using QueryTuple = QueryType<shared_model::interface::types::RoleIdType>;
750 : using PermissionTuple = boost::tuple<int>;
751 :
752 8 : auto cmd = (boost::format(
753 : R"(WITH has_perms AS (%s)
754 : SELECT role_id, perm FROM role
755 : RIGHT OUTER JOIN has_perms ON TRUE
756 8 : )") % checkAccountRolePermission(Role::kGetRoles))
757 8 : .str();
758 :
759 8 : return executeQuery<QueryTuple, PermissionTuple>(
760 : [&] {
761 8 : return (sql_.prepare << cmd,
762 8 : soci::use(creator_id_, "role_account_id"));
763 0 : },
764 : [&](auto range, auto &) {
765 6 : auto roles = boost::copy_range<
766 : std::vector<shared_model::interface::types::RoleIdType>>(
767 : range | boost::adaptors::transformed([](auto t) {
768 : return apply(t, [](auto &role_id) { return role_id; });
769 : }));
770 :
771 6 : return query_response_factory_->createRolesResponse(roles,
772 6 : query_hash_);
773 6 : },
774 8 : notEnoughPermissionsResponse(perm_converter_, Role::kGetRoles));
775 8 : }
776 :
777 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
778 : const shared_model::interface::GetRolePermissions &q) {
779 : using QueryTuple = QueryType<std::string>;
780 : using PermissionTuple = boost::tuple<int>;
781 :
782 5 : auto cmd = (boost::format(
783 : R"(WITH has_perms AS (%s),
784 : perms AS (SELECT permission FROM role_has_permissions
785 : WHERE role_id = :role_name)
786 : SELECT permission, perm FROM perms
787 : RIGHT OUTER JOIN has_perms ON TRUE
788 5 : )") % checkAccountRolePermission(Role::kGetRoles))
789 5 : .str();
790 :
791 5 : return executeQuery<QueryTuple, PermissionTuple>(
792 : [&] {
793 5 : return (sql_.prepare << cmd,
794 5 : soci::use(creator_id_, "role_account_id"),
795 5 : soci::use(q.roleId(), "role_name"));
796 0 : },
797 : [this, &q](auto range, auto &) {
798 3 : if (range.empty()) {
799 1 : return this->logAndReturnErrorResponse(
800 : QueryErrorType::kNoRoles,
801 1 : "{" + q.roleId() + ", " + creator_id_ + "}");
802 : }
803 :
804 : return apply(range.front(), [this](auto &permission) {
805 2 : return query_response_factory_->createRolePermissionsResponse(
806 2 : shared_model::interface::RolePermissionSet(permission),
807 2 : query_hash_);
808 : });
809 3 : },
810 5 : notEnoughPermissionsResponse(perm_converter_, Role::kGetRoles));
811 5 : }
812 :
813 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
814 : const shared_model::interface::GetAssetInfo &q) {
815 : using QueryTuple =
816 : QueryType<shared_model::interface::types::DomainIdType, uint32_t>;
817 : using PermissionTuple = boost::tuple<int>;
818 :
819 5 : auto cmd = (boost::format(
820 : R"(WITH has_perms AS (%s),
821 : perms AS (SELECT domain_id, precision FROM asset
822 : WHERE asset_id = :asset_id)
823 : SELECT domain_id, precision, perm FROM perms
824 : RIGHT OUTER JOIN has_perms ON TRUE
825 5 : )") % checkAccountRolePermission(Role::kReadAssets))
826 5 : .str();
827 :
828 5 : return executeQuery<QueryTuple, PermissionTuple>(
829 : [&] {
830 5 : return (sql_.prepare << cmd,
831 5 : soci::use(creator_id_, "role_account_id"),
832 5 : soci::use(q.assetId(), "asset_id"));
833 0 : },
834 : [this, &q](auto range, auto &) {
835 3 : if (range.empty()) {
836 2 : return this->logAndReturnErrorResponse(
837 : QueryErrorType::kNoAsset,
838 2 : "{" + q.assetId() + ", " + creator_id_ + "}");
839 : }
840 :
841 1 : return apply(range.front(),
842 : [this, &q](auto &domain_id, auto &precision) {
843 1 : return query_response_factory_->createAssetResponse(
844 1 : q.assetId(), domain_id, precision, query_hash_);
845 0 : });
846 3 : },
847 5 : notEnoughPermissionsResponse(perm_converter_, Role::kReadAssets));
848 5 : }
849 :
850 : QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
851 : const shared_model::interface::GetPendingTransactions &q) {
852 : std::vector<std::unique_ptr<shared_model::interface::Transaction>>
853 5 : response_txs;
854 : auto interface_txs =
855 5 : pending_txs_storage_->getPendingTransactions(creator_id_);
856 5 : response_txs.reserve(interface_txs.size());
857 :
858 5 : std::transform(interface_txs.begin(),
859 5 : interface_txs.end(),
860 5 : std::back_inserter(response_txs),
861 : [](auto &tx) { return clone(*tx); });
862 5 : return query_response_factory_->createTransactionsResponse(
863 5 : std::move(response_txs), query_hash_);
864 5 : }
865 :
866 : } // namespace ametsuchi
867 : } // namespace iroha
|