|
14 | 14 | #include "interfaces/commands/add_peer.hpp"
|
15 | 15 | #include "interfaces/commands/add_signatory.hpp"
|
16 | 16 | #include "interfaces/commands/append_role.hpp"
|
| 17 | +#include "interfaces/commands/compare_and_set_account_detail.hpp" |
17 | 18 | #include "interfaces/commands/create_account.hpp"
|
18 | 19 | #include "interfaces/commands/create_asset.hpp"
|
19 | 20 | #include "interfaces/commands/create_domain.hpp"
|
@@ -266,6 +267,71 @@ namespace {
|
266 | 267 | return query;
|
267 | 268 | }
|
268 | 269 |
|
| 270 | + shared_model::interface::types::DomainIdType getDomainFromName( |
| 271 | + const shared_model::interface::types::AccountIdType &account_id) { |
| 272 | + // TODO 03.10.18 andrei: IR-1728 Move getDomainFromName to shared_model |
| 273 | + std::vector<std::string> res; |
| 274 | + boost::split(res, account_id, boost::is_any_of("@")); |
| 275 | + return res.at(1); |
| 276 | + } |
| 277 | + |
| 278 | + /** |
| 279 | + * Generate an SQL subquery which checks if creator has corresponding |
| 280 | + * permissions for target account |
| 281 | + * It verifies individual, domain, and global permissions, and returns true if |
| 282 | + * any of listed permissions is present |
| 283 | + */ |
| 284 | + auto hasQueryPermission( |
| 285 | + const shared_model::interface::types::AccountIdType &creator, |
| 286 | + const shared_model::interface::types::AccountIdType &target_account, |
| 287 | + shared_model::interface::permissions::Role indiv_permission_id, |
| 288 | + shared_model::interface::permissions::Role all_permission_id, |
| 289 | + shared_model::interface::permissions::Role domain_permission_id, |
| 290 | + const shared_model::interface::types::DomainIdType &creator_domain, |
| 291 | + const shared_model::interface::types::DomainIdType |
| 292 | + &target_account_domain) { |
| 293 | + const auto bits = shared_model::interface::RolePermissionSet::size(); |
| 294 | + const auto perm_str = |
| 295 | + shared_model::interface::RolePermissionSet({indiv_permission_id}) |
| 296 | + .toBitstring(); |
| 297 | + const auto all_perm_str = |
| 298 | + shared_model::interface::RolePermissionSet({all_permission_id}) |
| 299 | + .toBitstring(); |
| 300 | + const auto domain_perm_str = |
| 301 | + shared_model::interface::RolePermissionSet({domain_permission_id}) |
| 302 | + .toBitstring(); |
| 303 | + |
| 304 | + boost::format cmd(R"( |
| 305 | + has_indiv_perm AS ( |
| 306 | + SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%)) |
| 307 | + & '%3%') = '%3%' FROM role_has_permissions AS rp |
| 308 | + JOIN account_has_roles AS ar on ar.role_id = rp.role_id |
| 309 | + WHERE ar.account_id = %2% |
| 310 | + ), |
| 311 | + has_all_perm AS ( |
| 312 | + SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%)) |
| 313 | + & '%4%') = '%4%' FROM role_has_permissions AS rp |
| 314 | + JOIN account_has_roles AS ar on ar.role_id = rp.role_id |
| 315 | + WHERE ar.account_id = %2% |
| 316 | + ), |
| 317 | + has_domain_perm AS ( |
| 318 | + SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%)) |
| 319 | + & '%5%') = '%5%' FROM role_has_permissions AS rp |
| 320 | + JOIN account_has_roles AS ar on ar.role_id = rp.role_id |
| 321 | + WHERE ar.account_id = %2% |
| 322 | + ), |
| 323 | + has_query_perm AS ( |
| 324 | + SELECT (%2% = %6% AND (SELECT * FROM has_indiv_perm)) |
| 325 | + OR (SELECT * FROM has_all_perm) |
| 326 | + OR (%7% = %8% AND (SELECT * FROM has_domain_perm)) AS perm |
| 327 | + ) |
| 328 | + )"); |
| 329 | + |
| 330 | + return (cmd % bits % creator % perm_str % all_perm_str % domain_perm_str |
| 331 | + % target_account % creator_domain % target_account_domain) |
| 332 | + .str(); |
| 333 | + } |
| 334 | + |
269 | 335 | std::string checkAccountDomainRoleOrGlobalRolePermission(
|
270 | 336 | shared_model::interface::permissions::Role global_permission,
|
271 | 337 | shared_model::interface::permissions::Role domain_permission,
|
@@ -797,6 +863,51 @@ namespace iroha {
|
797 | 863 | ELSE 1
|
798 | 864 | END AS result)";
|
799 | 865 |
|
| 866 | + const std::string PostgresCommandExecutor::compareAndSetAccountDetailBase = |
| 867 | + R"(PREPARE %s (text, text, text, text, text, text, text) AS |
| 868 | + WITH %s |
| 869 | + old_value AS |
| 870 | + ( |
| 871 | + SELECT * |
| 872 | + FROM account |
| 873 | + WHERE |
| 874 | + account_id = $2 |
| 875 | + AND CASE |
| 876 | + WHEN data ? $1 AND data->$1 ?$3 |
| 877 | + THEN |
| 878 | + CASE |
| 879 | + WHEN $5 IS NOT NULL THEN data->$1->$3 = $5::jsonb |
| 880 | + ELSE FALSE |
| 881 | + END |
| 882 | + ELSE TRUE |
| 883 | + END |
| 884 | + ), |
| 885 | + inserted AS |
| 886 | + ( |
| 887 | + UPDATE account |
| 888 | + SET data = jsonb_set( |
| 889 | + CASE |
| 890 | + WHEN data ? $1 THEN data |
| 891 | + ELSE jsonb_set(data, array[$1], '{}') |
| 892 | + END, |
| 893 | + array[$1, $3], $4::jsonb |
| 894 | + ) |
| 895 | + WHERE |
| 896 | + EXISTS (SELECT * FROM old_value) |
| 897 | + AND account_id = $2 |
| 898 | + %s |
| 899 | + RETURNING (1) |
| 900 | + ) |
| 901 | + SELECT CASE |
| 902 | + WHEN EXISTS (SELECT * FROM inserted) THEN 0 |
| 903 | + WHEN NOT EXISTS |
| 904 | + (SELECT * FROM account WHERE account_id=$2) THEN 3 |
| 905 | + WHEN NOT EXISTS (SELECT * FROM old_value) THEN 4 |
| 906 | + %s |
| 907 | + ELSE 1 |
| 908 | + END |
| 909 | + AS result)"; |
| 910 | + |
800 | 911 | std::string CommandError::toString() const {
|
801 | 912 | return (boost::format("%s: %d with extra info '%s'") % command_name
|
802 | 913 | % error_code % error_extra)
|
@@ -1228,6 +1339,43 @@ namespace iroha {
|
1228 | 1339 | sql_, cmd.str(), "TransferAsset", std::move(str_args));
|
1229 | 1340 | }
|
1230 | 1341 |
|
| 1342 | + CommandResult PostgresCommandExecutor::operator()( |
| 1343 | + const shared_model::interface::CompareAndSetAccountDetail &command) { |
| 1344 | + auto &account_id = command.accountId(); |
| 1345 | + auto &key = command.key(); |
| 1346 | + auto &value = command.value(); |
| 1347 | + auto &old_value = command.oldValue(); |
| 1348 | + |
| 1349 | + auto cmd = boost::format( |
| 1350 | + "EXECUTE %1% ('%2%', '%3%', '%4%', '%5%', %6%, '%7%', '%8%')"); |
| 1351 | + |
| 1352 | + appendCommandName("compareAndSetAccountDetail", cmd, do_validation_); |
| 1353 | + |
| 1354 | + std::string new_json_value = "\"" + value + "\""; |
| 1355 | + std::string expected_json_value = "NULL"; |
| 1356 | + |
| 1357 | + if (old_value) { |
| 1358 | + expected_json_value = "'\"" + old_value.get() + "\"'"; |
| 1359 | + } |
| 1360 | + |
| 1361 | + cmd = (cmd % creator_account_id_ % account_id % key % new_json_value |
| 1362 | + % expected_json_value % getDomainFromName(creator_account_id_) |
| 1363 | + % getDomainFromName(account_id)); |
| 1364 | + |
| 1365 | + auto str_args = [&account_id, &key, &new_json_value, &old_value] { |
| 1366 | + return getQueryArgsStringBuilder() |
| 1367 | + .append("account_id", account_id) |
| 1368 | + .append("key", key) |
| 1369 | + .append("value", new_json_value) |
| 1370 | + .append("old_value", |
| 1371 | + old_value ? "\"" + old_value.get() + "\"" : "NULL") |
| 1372 | + .finalize(); |
| 1373 | + }; |
| 1374 | + |
| 1375 | + return executeQuery( |
| 1376 | + sql_, cmd.str(), "compareAndSetAccountDetail", std::move(str_args)); |
| 1377 | + } |
| 1378 | + |
1231 | 1379 | void PostgresCommandExecutor::prepareStatements(soci::session &sql) {
|
1232 | 1380 | std::vector<PreparedStatement> statements;
|
1233 | 1381 |
|
@@ -1598,6 +1746,45 @@ namespace iroha {
|
1598 | 1746 | R"( AND (SELECT * FROM has_perm))",
|
1599 | 1747 | R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
|
1600 | 1748 |
|
| 1749 | + statements.push_back( |
| 1750 | + {"compareAndSetAccountDetail", |
| 1751 | + compareAndSetAccountDetailBase, |
| 1752 | + {(boost::format(R"( |
| 1753 | + has_role_perm AS (%s), |
| 1754 | + has_grantable_perm AS (%s), |
| 1755 | + %s, |
| 1756 | + has_perm AS (SELECT CASE |
| 1757 | + WHEN (SELECT * FROM has_query_perm) THEN |
| 1758 | + CASE |
| 1759 | + WHEN (SELECT * FROM has_grantable_perm) |
| 1760 | + THEN true |
| 1761 | + WHEN ($1 = $2) THEN true |
| 1762 | + WHEN (SELECT * FROM has_role_perm) |
| 1763 | + THEN true |
| 1764 | + ELSE false END |
| 1765 | + ELSE false END |
| 1766 | + ), |
| 1767 | + )") |
| 1768 | + % checkAccountRolePermission( |
| 1769 | + shared_model::interface::permissions::Role::kSetDetail, "$1") |
| 1770 | + % checkAccountGrantablePermission( |
| 1771 | + shared_model::interface::permissions::Grantable:: |
| 1772 | + kSetMyAccountDetail, |
| 1773 | + "$1", |
| 1774 | + "$2") |
| 1775 | + % hasQueryPermission( |
| 1776 | + "$1", |
| 1777 | + "$2", |
| 1778 | + shared_model::interface::permissions::Role::kGetMyAccDetail, |
| 1779 | + shared_model::interface::permissions::Role::kGetAllAccDetail, |
| 1780 | + shared_model::interface::permissions::Role:: |
| 1781 | + kGetDomainAccDetail, |
| 1782 | + "$6", |
| 1783 | + "$7")) |
| 1784 | + .str(), |
| 1785 | + R"( AND (SELECT * FROM has_perm))", |
| 1786 | + R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}}); |
| 1787 | + |
1601 | 1788 | for (const auto &st : statements) {
|
1602 | 1789 | prepareStatement(sql, st);
|
1603 | 1790 | }
|
|
0 commit comments