From 4f94246b6f22aa2ca138662d9e4aeb1e8c2e827a Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 7 Mar 2018 04:50:20 -0300 Subject: [PATCH] Add permission api --- contracts/eosiolib/datastream.hpp | 23 ++++ contracts/eosiolib/permission.h | 21 ++++ contracts/test_api/test_api.cpp | 5 + contracts/test_api/test_api.hpp | 4 + contracts/test_api/test_permission.cpp | 33 ++++++ libraries/chain/chain_controller.cpp | 21 ++++ .../include/eosio/chain/chain_controller.hpp | 10 ++ libraries/chain/wasm_interface.cpp | 26 +++++ tests/api_tests/api_tests.cpp | 104 ++++++++++++++++++ 9 files changed, 247 insertions(+) create mode 100644 contracts/eosiolib/permission.h create mode 100644 contracts/test_api/test_permission.cpp diff --git a/contracts/eosiolib/datastream.hpp b/contracts/eosiolib/datastream.hpp index 91166e57d..701dfada2 100644 --- a/contracts/eosiolib/datastream.hpp +++ b/contracts/eosiolib/datastream.hpp @@ -141,6 +141,29 @@ class datastream { size_t _size; }; +/** + * Serialize a public_key into a stream + * @brief Serialize a public_key + * @param ds stream to write + * @param pubkey value to serialize + */ +template +inline datastream& operator<<(datastream& ds, const public_key pubkey) { + ds.write( (const char*)&pubkey, sizeof(pubkey)); + return ds; +} +/** + * Deserialize a public_key from a stream + * @brief Deserialize a public_key + * @param ds stream to read + * @param pubkey destination for deserialized value + */ +template +inline datastream& operator>>(datastream& ds, public_key& pubkey) { + ds.read((char*)&pubkey, sizeof(pubkey)); + return ds; +} + /** * Serialize a key256 into a stream * @brief Serialize a key256 diff --git a/contracts/eosiolib/permission.h b/contracts/eosiolib/permission.h new file mode 100644 index 000000000..6513ab938 --- /dev/null +++ b/contracts/eosiolib/permission.h @@ -0,0 +1,21 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once +#include + +extern "C" { + /** + * Checks if a set of public keys can authorize an account permission + * @brief Checks if a set of public keys can authorize an account permission + * @param account - the account owner of the permission + * @param permission - the permission name to check for authorization + * @param pubkeys - a pointer to an array of public keys (public_key) + * @param pubkeys_len - the lenght of the array of public keys + * @return 1 if the account permission can be authorized, 0 otherwise + */ + int32_t check_authorization( account_name account, permission_name permission, char* pubkeys, uint32_t pubkeys_len ); +} + + diff --git a/contracts/test_api/test_api.cpp b/contracts/test_api/test_api.cpp index cb0933981..f55e83979 100644 --- a/contracts/test_api/test_api.cpp +++ b/contracts/test_api/test_api.cpp @@ -16,6 +16,7 @@ #include "test_chain.cpp" #include "test_transaction.cpp" #include "test_checktime.cpp" +#include "test_permission.cpp" extern "C" { @@ -148,6 +149,7 @@ extern "C" { // test checktime WASM_TEST_HANDLER(test_checktime, checktime_pass); WASM_TEST_HANDLER(test_checktime, checktime_failure); + /* // test softfloat WASM_TEST_HANDLER(test_softfloat, test_f32_add); @@ -157,6 +159,9 @@ extern "C" { WASM_TEST_HANDLER(test_softfloat, test_f32_min); */ + // test permission + WASM_TEST_HANDLER(test_permission, check_authorization); + //unhandled test call eosio_assert(false, "Unknown Test"); diff --git a/contracts/test_api/test_api.hpp b/contracts/test_api/test_api.hpp index 084d915e1..98494ef76 100644 --- a/contracts/test_api/test_api.hpp +++ b/contracts/test_api/test_api.hpp @@ -277,3 +277,7 @@ struct test_softfloat { static void test_f32_min(); }; */ + +struct test_permission { + static void check_authorization(); +}; diff --git a/contracts/test_api/test_permission.cpp b/contracts/test_api/test_permission.cpp new file mode 100644 index 000000000..4b1d9f2e2 --- /dev/null +++ b/contracts/test_api/test_permission.cpp @@ -0,0 +1,33 @@ +/** + * @file action_test.cpp + * @copyright defined in eos/LICENSE.txt + */ +#include +#include + +#include +#include +#include +#include +#include + +#include "test_api.hpp" + +using namespace eosio; + +struct check_auth { + account_name account; + permission_name permission; + vector pubkeys; + + EOSLIB_SERIALIZE( check_auth, (account)(permission)(pubkeys) ) +}; + +void test_permission::check_authorization() { + auto params = unpack_action_data(); + + uint64_t res64 = (uint64_t)::check_authorization( params.account, params.permission, + (char*)params.pubkeys.data(), params.pubkeys.size()*sizeof(public_key) ); + + store_i64(current_receiver(), current_receiver(), current_receiver(), &res64, sizeof(uint64_t)); +} diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index f86ff373f..8abb09528 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -842,6 +842,7 @@ flat_set chain_controller::get_required_keys(const transaction& return checker.used_keys(); } + class permission_visitor { public: permission_visitor(const chain_controller& controller) : _chain_controller(controller) {} @@ -863,6 +864,7 @@ time_point chain_controller::check_authorization( const vector& actions, const flat_set& provided_keys, bool allow_unused_signatures, flat_set provided_accounts )const + { auto checker = make_auth_checker( [&](const permission_level& p){ return get_permission(p).auth; }, permission_visitor(*this), @@ -963,6 +965,25 @@ time_point chain_controller::check_authorization( const vector& actions, return max_delay; } +bool chain_controller::check_authorization( account_name account, permission_name permission, + flat_set provided_keys, + bool allow_unused_signatures)const +{ + auto checker = make_auth_checker( [&](const permission_level& p){ return get_permission(p).auth; }, + get_global_properties().configuration.max_authority_depth, + provided_keys); + + auto satisfied = checker.satisfied({account, permission}); + + if (satisfied && !allow_unused_signatures) { + EOS_ASSERT(checker.all_keys_used(), tx_irrelevant_sig, + "irrelevant signatures from these keys: ${keys}", + ("keys", checker.unused_keys())); + } + + return satisfied; +} + time_point chain_controller::check_transaction_authorization(const transaction& trx, const vector& signatures, const vector& cfd, diff --git a/libraries/chain/include/eosio/chain/chain_controller.hpp b/libraries/chain/include/eosio/chain/chain_controller.hpp index 55aae2bf1..6e43ec46d 100644 --- a/libraries/chain/include/eosio/chain/chain_controller.hpp +++ b/libraries/chain/include/eosio/chain/chain_controller.hpp @@ -294,6 +294,16 @@ namespace eosio { namespace chain { flat_set provided_accounts = flat_set() )const; + /** + * @param account - the account owner of the permission + * @param permission - the permission name to check for authorization + * @param provided_keys - a set of public keys + * + * @return true if the provided keys are sufficient to authorize the account permission + */ + bool check_authorization( account_name account, permission_name permission, + flat_set provided_keys, + bool allow_unused_signatures)const; private: const apply_handler* find_apply_handler( account_name contract, scope_name scope, action_name act )const; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index adc5d13b6..c894214ba 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -752,6 +752,28 @@ class crypto_api : public context_aware_api { } }; +class permission_api : public context_aware_api { + public: + using context_aware_api::context_aware_api; + + bool check_authorization( account_name account, permission_name permission, array_ptr packed_pubkeys, size_t datalen) { + + vector pub_keys; + datastream ds( packed_pubkeys, datalen ); + while(ds.remaining()) { + public_key_type pub; + fc::raw::unpack(ds, pub); + pub_keys.emplace_back(pub); + } + + return context.controller.check_authorization( + account, permission, + {pub_keys.begin(), pub_keys.end()}, + false + ); + } +}; + class string_api : public context_aware_api { public: using context_aware_api::context_aware_api; @@ -1501,6 +1523,10 @@ REGISTER_INTRINSICS(crypto_api, (ripemd160, void(int, int, int) ) ); +REGISTER_INTRINSICS(permission_api, + (check_authorization, int(int64_t, int64_t, int, int)) +); + REGISTER_INTRINSICS(string_api, (assert_is_utf8, void(int, int, int) ) ); diff --git a/tests/api_tests/api_tests.cpp b/tests/api_tests/api_tests.cpp index ce8bcf7ac..cde887595 100644 --- a/tests/api_tests/api_tests.cpp +++ b/tests/api_tests/api_tests.cpp @@ -81,7 +81,13 @@ struct test_chain_action { FC_REFLECT_TEMPLATE((uint64_t T), test_chain_action, BOOST_PP_SEQ_NIL); +struct check_auth { + account_name account; + permission_name permission; + vector pubkeys; +}; +FC_REFLECT(check_auth, (account)(permission)(pubkeys) ); bool expect_assert_message(const fc::exception& ex, string expected) { BOOST_TEST_MESSAGE("LOG : " << "expected: " << expected << ", actual: " << ex.get_log().at(0).get_message()); @@ -1207,6 +1213,104 @@ BOOST_FIXTURE_TEST_CASE(types_tests, tester) { try { CALL_TEST_FUNCTION( *this, "test_types", "name_class", {}); } FC_LOG_AND_RETHROW() } +/************************************************************************************* + * permission_tests test case + *************************************************************************************/ +BOOST_FIXTURE_TEST_CASE(permission_tests, tester) { try { + produce_blocks(1); + create_account( N(testapi) ); + + produce_blocks(1); + set_code( N(testapi), test_api_wast ); + produce_blocks(1); + + auto get_result_uint64 = [&]() -> uint64_t { + const auto& db = control->get_database(); + const auto* t_id = db.find(boost::make_tuple(N(testapi), N(testapi), N(testapi))); + FC_ASSERT(t_id != 0, "Table id not found"); + + const auto& idx = db.get_index(); + + auto itr = idx.lower_bound(boost::make_tuple(t_id->id)); + FC_ASSERT( itr != idx.end() && itr->t_id == t_id->id, "lower_bound failed"); + + FC_ASSERT( 0 == itr->value.size(), "unexpected result size"); + return itr->primary_key; + }; + + CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", + fc::raw::pack( check_auth { + .account = N(testapi), + .permission = N(active), + .pubkeys = { + get_public_key(N(testapi), "active") + } + }) + ); + BOOST_CHECK_EQUAL( uint64_t(1), get_result_uint64() ); + + CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", + fc::raw::pack( check_auth { + .account = N(testapi), + .permission = N(active), + .pubkeys = { + public_key_type(string("EOS7GfRtyDWWgxV88a5TRaYY59XmHptyfjsFmHHfioGNJtPjpSmGX")) + } + }) + ); + BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() ); + + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", + fc::raw::pack( check_auth { + .account = N(testapi), + .permission = N(active), + .pubkeys = { + get_public_key(N(testapi), "active"), + public_key_type(string("EOS7GfRtyDWWgxV88a5TRaYY59XmHptyfjsFmHHfioGNJtPjpSmGX")) + } + })), tx_irrelevant_sig, + [](const tx_irrelevant_sig& e) { + return expect_assert_message(e, "irrelevant signatures from these keys: [\"EOS7GfRtyDWWgxV88a5TRaYY59XmHptyfjsFmHHfioGNJtPjpSmGX\"]"); + } + ); + + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", + fc::raw::pack( check_auth { + .account = N(noname), + .permission = N(active), + .pubkeys = { + get_public_key(N(testapi), "active") + } + })), fc::exception, + [](const fc::exception& e) { + return expect_assert_message(e, "unknown key"); + } + ); + + CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", + fc::raw::pack( check_auth { + .account = N(testapi), + .permission = N(active), + .pubkeys = {} + }) + ); + BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() ); + + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", + fc::raw::pack( check_auth { + .account = N(testapi), + .permission = N(noname), + .pubkeys = { + get_public_key(N(testapi), "active") + } + })), fc::exception, + [](const fc::exception& e) { + return expect_assert_message(e, "unknown key"); + } + ); + +} FC_LOG_AND_RETHROW() } + #if 0 /************************************************************************************* * privileged_tests test case -- GitLab