diff --git a/api_access.json b/api_access.json new file mode 100644 index 0000000000..71285330b5 --- /dev/null +++ b/api_access.json @@ -0,0 +1,65 @@ +{ + "permission_map": + [ + [ + "*", + { + "password_hash_b64": "*", + "password_salt_b64": "*", + "allowed_apis": [ + "database_api", + "network_broadcast_api", + "history_api", + "block_api", + "asset_api", + "orders_api" + ] + } + ] + ], + "permission_map_signed_default": + [ + { + "required_lifetime_member": false, + "required_registrar": "registrar_name1", + "required_referrer": "", + "allowed_apis": + [ + "database_api", + "history_api", + "block_api", + "orders_api" + ] + }, + { + "required_lifetime_member": true, + "required_registrar": "registrar_name2", + "required_referrer": "", + "allowed_apis": + [ + "database_api", + "history_api", + "block_api", + "orders_api" + ] + } + ], + "permission_map_signed_user": + [ + [ + "alice", + { + "required_lifetime_member": false, + "required_registrar": "", + "allowed_apis": + [ + "database_api", + "history_api", + "block_api", + "asset_api", + "orders_api" + ] + } + ] + ] +} diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index d661bf4735..01094c5a7d 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -35,10 +35,14 @@ #include #include #include +#include #include #include #include +#include +#include +#include template class fc::api; template class fc::api; @@ -84,6 +88,116 @@ namespace graphene { namespace app { return true; } + bool login_api::login_signed( const string& b64_encoded_trx ) + { + const string trx_json = fc::base64_decode( b64_encoded_trx ); + auto var = fc::json::from_string( trx_json ); + auto trx = var.as( 200 ); + + auto db = _app.chain_database(); + int64_t offset( (trx.expiration - db->head_block_time()).to_seconds() ); + if( offset > 5*60 || offset < 0 ) + return false; + + auto op = trx.operations[0]; + if( op.which() != operation::tag::value ) // only transfer op for validation + return false; + + const auto acc_id = op.get().from; + const auto to = op.get().to; + if( acc_id != to ) // prevent MITM attacks + return false; + + const auto& signature_keys = trx.get_signature_keys( db->get_chain_id() ); + if( signature_keys.empty() ) + return false; + + const auto& public_key = *signature_keys.begin(); + + auto key_refs = (*_database_api)->get_key_references( {public_key} )[0]; + if( std::find( key_refs.begin(), key_refs.end(), acc_id ) == key_refs.end() ) + return false; + + const auto& acc = acc_id(*db); + optional< api_access_info_signed_variant > api_access_info_var = _app.get_api_access_info_signed( acc.name ); + if( !api_access_info_var ) + return false; + + if( api_access_info_var->which() == api_access_info_signed_variant::tag::value ) + { + const auto& api_access_info = api_access_info_var->get(); + if( !verify_api_access_info_signed( acc, api_access_info ) ) + return false; + + for( const auto& api : api_access_info.allowed_apis ) + enable_api( api ); + + return true; + } + else // api_access_info_var.which() == api_access_info_signed_variant::tag>::value + { + const auto& api_access_info_vec = api_access_info_var->get>(); + for( const auto& api_access_info : api_access_info_vec ) + { + if( !verify_api_access_info_signed( acc, api_access_info ) ) + continue; + + for( const auto& api : api_access_info.allowed_apis ) + enable_api( api ); + + return true; + } + return false; + } + } + + bool login_api::verify_api_access_info_signed( const account_object& acc, + const api_access_info_signed& api_access_info ) + { + auto db = _app.chain_database(); + + if( api_access_info.required_lifetime_member && !acc.is_lifetime_member() ) + return false; + + const auto& required_registrar_name = api_access_info.required_registrar; + bool registrar_required = required_registrar_name != "" ? true : false; + + const auto& required_referrer_name = api_access_info.required_referrer; + bool referrer_required = required_referrer_name != "" ? true : false; + + if( !referrer_required && !registrar_required ) + return true; + + + bool has_required_registrar = true; + if( registrar_required ) + { + const string acc_registrar_name = acc.registrar(*db).name; + + string acc_original_registrar_name; + if( acc.original_registrar ) + acc_original_registrar_name = (*acc.original_registrar)(*db).name; + + has_required_registrar = required_registrar_name == acc_registrar_name + || required_registrar_name == acc_original_registrar_name; + } + + bool has_required_referrer = true; + if( referrer_required ) + { + const string acc_referrer_name = acc.referrer(*db).name; + + string acc_original_referrer_name; + if( acc.original_referrer ) + acc_original_referrer_name = (*acc.original_registrar)(*db).name; + + has_required_referrer = required_referrer_name == acc_referrer_name + || required_referrer_name == acc_original_referrer_name; + } + + return has_required_registrar && has_required_referrer; + } + void login_api::enable_api( const std::string& api_name ) { if( api_name == "database_api" ) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 4f835ff10d..f4b9195e89 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -238,24 +238,32 @@ void application_impl::new_connection( const fc::http::websocket_connection_ptr& std::string username = "*"; std::string password = "*"; - // Try to extract login information from "Authorization" header if present std::string auth = c->get_request_header("Authorization"); - if( boost::starts_with(auth, "Basic ") ) { - - FC_ASSERT( auth.size() > 6 ); - auto user_pass = fc::base64_decode(auth.substr(6)); + if( !boost::starts_with(auth, "Basic ") ) { + login->login(username, password); + return; + } - std::vector parts; - boost::split( parts, user_pass, boost::is_any_of(":") ); + FC_ASSERT( auth.size() > 6 ); + auto decoded_auth = fc::base64_decode(auth.substr(6)); - FC_ASSERT(parts.size() == 2); + std::vector parts; + boost::split( parts, decoded_auth, boost::is_any_of(":") ); + size_t parts_size = parts.size(); + FC_ASSERT( parts_size == 2 ); + if( parts[0] != "Signature" ) + { username = parts[0]; password = parts[1]; + login->login( username, password ); + } + else + { + string base64_encoded_trx = parts[1]; + login->login_signed( base64_encoded_trx ); } - - login->login(username, password); } void application_impl::reset_websocket_server() @@ -373,7 +381,7 @@ void application_impl::startup() modified_genesis = true; ilog( - "Used genesis timestamp: ${timestamp} (PLEASE RECORD THIS)", + "Used genesis timestamp: ${timestamp} (PLEASE RECORD THIS)", ("timestamp", genesis.initial_timestamp.to_iso_string()) ); } @@ -481,7 +489,7 @@ void application_impl::startup() fc::path api_access_file = _options->at("api-access").as(); - FC_ASSERT( fc::exists(api_access_file), + FC_ASSERT( fc::exists(api_access_file), "Failed to load file from ${path}", ("path", api_access_file) ); _apiaccess = fc::json::from_file( api_access_file ).as( 20 ); @@ -521,11 +529,33 @@ optional< api_access_info > application_impl::get_api_access_info(const string& return it->second; } +optional< api_access_info_signed_variant > application_impl::get_api_access_info_signed(const string& username)const +{ + auto it = _apiaccess.permission_map_signed_user.find( username ); + if( it != _apiaccess.permission_map_signed_user.end() ) + return it->second; + + if( !_apiaccess.permission_map_signed_default.empty() ) + return _apiaccess.permission_map_signed_default; + + return optional< api_access_info_signed_variant >(); +} + void application_impl::set_api_access_info(const string& username, api_access_info&& permissions) { _apiaccess.permission_map.insert(std::make_pair(username, std::move(permissions))); } +void application_impl::set_api_access_info_signed_default(vector&& permissions) +{ + _apiaccess.permission_map_signed_default = std::move( permissions ); +} + +void application_impl::set_api_access_info_signed_user(const string& username, api_access_info_signed&& permissions) +{ + _apiaccess.permission_map_signed_user.insert( std::make_pair(username, std::move(permissions) ) ); +} + /** * If delegate has the item, the network has no need to fetch it. */ @@ -965,8 +995,6 @@ uint8_t application_impl::get_current_block_interval_in_seconds() const return _chain_db->get_global_properties().parameters.block_interval; } - - } } } // namespace graphene namespace app namespace detail namespace graphene { namespace app { @@ -1120,11 +1148,26 @@ optional< api_access_info > application::get_api_access_info( const string& user return my->get_api_access_info( username ); } +optional< api_access_info_signed_variant > application::get_api_access_info_signed( const string& username )const +{ + return my->get_api_access_info_signed( username ); +} + void application::set_api_access_info(const string& username, api_access_info&& permissions) { my->set_api_access_info(username, std::move(permissions)); } +void application::set_api_access_info_signed_default(vector&& permissions) +{ + my->set_api_access_info_signed_default( std::move(permissions) ); +} + +void application::set_api_access_info_signed_user(const string& username, api_access_info_signed&& permissions) +{ + my->set_api_access_info_signed_user( username, std::move(permissions) ); +} + bool application::is_finished_syncing() const { return my->_is_finished_syncing; diff --git a/libraries/app/application_impl.hxx b/libraries/app/application_impl.hxx index 175648e10f..9a467a636e 100644 --- a/libraries/app/application_impl.hxx +++ b/libraries/app/application_impl.hxx @@ -47,8 +47,14 @@ class application_impl : public net::node_delegate fc::optional< api_access_info > get_api_access_info(const string& username)const; + fc::optional< api_access_info_signed_variant > get_api_access_info_signed(const string& username)const; + void set_api_access_info(const string& username, api_access_info&& permissions); + void set_api_access_info_signed_default(std::vector&& permissions); + + void set_api_access_info_signed_user(const string& username, api_access_info_signed&& permissions); + /** * If delegate has the item, the network has no need to fetch it. */ diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index c85fab14b2..fa1f9071d3 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -549,6 +549,8 @@ namespace graphene { namespace app { * Other APIs may not be accessible until the client has sucessfully authenticated. */ bool login(const string& user, const string& password); + bool login_signed(const string& b64_encoded_trx); + /// @brief Retrieve the network block API fc::api block()const; /// @brief Retrieve the network broadcast API @@ -582,6 +584,9 @@ namespace graphene { namespace app { optional< fc::api > _asset_api; optional< fc::api > _orders_api; optional< fc::api > _debug_api; + + bool verify_api_access_info_signed( const account_object& acc, + const api_access_info_signed& api_access_info ); }; }} // graphene::app @@ -659,4 +664,5 @@ FC_API(graphene::app::login_api, (asset) (orders) (debug) + (login_signed) ) diff --git a/libraries/app/include/graphene/app/api_access.hpp b/libraries/app/include/graphene/app/api_access.hpp index 8dbad1a1d9..ff83bf211f 100644 --- a/libraries/app/include/graphene/app/api_access.hpp +++ b/libraries/app/include/graphene/app/api_access.hpp @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include @@ -38,11 +39,26 @@ struct api_access_info std::vector< std::string > allowed_apis; }; +struct api_access_info_signed +{ + bool required_lifetime_member; + std::string required_registrar; + std::string required_referrer; + std::vector< std::string > allowed_apis; +}; + struct api_access { std::map< std::string, api_access_info > permission_map; + std::vector< api_access_info_signed > permission_map_signed_default; + std::map< std::string, api_access_info_signed > permission_map_signed_user; }; +typedef fc::static_variant< + api_access_info_signed, + std::vector< api_access_info_signed > +> api_access_info_signed_variant; + } } // graphene::app FC_REFLECT( graphene::app::api_access_info, @@ -51,6 +67,17 @@ FC_REFLECT( graphene::app::api_access_info, (allowed_apis) ) +FC_REFLECT( graphene::app::api_access_info_signed, + (required_lifetime_member) + (required_registrar) + (required_referrer) + (allowed_apis) + ) + FC_REFLECT( graphene::app::api_access, (permission_map) + (permission_map_signed_default) + (permission_map_signed_user) ) + +FC_REFLECT_TYPENAME( graphene::app::api_access_info_signed_variant ) diff --git a/libraries/app/include/graphene/app/application.hpp b/libraries/app/include/graphene/app/application.hpp index 4df4cf6100..c575e6be17 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -116,7 +116,10 @@ namespace graphene { namespace app { void set_api_limit(); void set_block_production(bool producing_blocks); fc::optional< api_access_info > get_api_access_info( const string& username )const; + fc::optional< api_access_info_signed_variant > get_api_access_info_signed( const string& username )const; void set_api_access_info(const string& username, api_access_info&& permissions); + void set_api_access_info_signed_default(std::vector&& permissions); + void set_api_access_info_signed_user(const string& username, api_access_info_signed&& permissions); bool is_finished_syncing()const; /// Emitted when syncing finishes (is_finished_syncing will return true) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 674de46c43..ccfc233f09 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -185,6 +185,7 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio { obj.registrar = o.registrar; obj.referrer = o.referrer; + obj.original_referrer = o.referrer; obj.lifetime_referrer = o.referrer(d).lifetime_referrer; const auto& params = global_properties.parameters; @@ -424,6 +425,7 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator: // Upgrade to lifetime member. I don't care what the account was before. a.statistics(d).process_fees(a, d); a.membership_expiration_date = time_point_sec::maximum(); + a.original_registrar = a.registrar; a.referrer = a.registrar = a.lifetime_referrer = a.get_id(); a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage; } else if( a.is_annual_member(d.head_block_time()) ) { diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 70aa0f78e9..3dbb4d3559 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -171,6 +171,10 @@ namespace graphene { namespace chain { account_id_type referrer; /// The lifetime member at the top of the referral tree. Receives a percentage of referral rewards. account_id_type lifetime_referrer; + /// The original registrar of this account, this value is set when account_upgrade_operation is performed + optional original_registrar; + /// The original referrer of this account, this value is set when account_upgrade_operation is performed + optional original_referrer; /// Percentage of fee which should go to network. uint16_t network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; @@ -443,13 +447,36 @@ namespace graphene { namespace chain { }} -MAP_OBJECT_ID_TO_TYPE(graphene::chain::account_object) -MAP_OBJECT_ID_TO_TYPE(graphene::chain::account_balance_object) -MAP_OBJECT_ID_TO_TYPE(graphene::chain::account_statistics_object) - -FC_REFLECT_TYPENAME( graphene::chain::account_object ) -FC_REFLECT_TYPENAME( graphene::chain::account_balance_object ) -FC_REFLECT_TYPENAME( graphene::chain::account_statistics_object ) +FC_REFLECT_DERIVED( graphene::chain::account_object, + (graphene::db::object), + (membership_expiration_date)(registrar)(referrer)(lifetime_referrer) + (network_fee_percentage)(lifetime_referrer_fee_percentage)(referrer_rewards_percentage) + (name)(owner)(active)(options)(statistics)(whitelisting_accounts)(blacklisting_accounts) + (whitelisted_accounts)(blacklisted_accounts) + (cashback_vb) + (owner_special_authority)(active_special_authority) + (top_n_control_flags) + (allowed_assets) + (original_registrar) + (original_referrer) + ) + +FC_REFLECT_DERIVED( graphene::chain::account_balance_object, + (graphene::db::object), + (owner)(asset_type)(balance)(maintenance_flag) ) + +FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, + (graphene::chain::object), + (owner)(name) + (most_recent_op) + (total_ops)(removed_ops) + (total_core_in_orders) + (core_in_balance) + (has_cashback_vb) + (is_voting) + (lifetime_fees_paid) + (pending_fees)(pending_vested_fees) + ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::account_object ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::account_balance_object ) diff --git a/tests/tests/login_signed_tests.cpp b/tests/tests/login_signed_tests.cpp new file mode 100644 index 0000000000..89393939b0 --- /dev/null +++ b/tests/tests/login_signed_tests.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2019 Blockchain Projects B.V. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include + +#include + +#include +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; +using namespace graphene::app; + + +struct login_signed_fixture : public database_fixture +{ + +}; + +BOOST_FIXTURE_TEST_SUITE( login_signed_tests, login_signed_fixture ) + +BOOST_AUTO_TEST_CASE( fail_with_timestamp_too_fresh ) +{ try { + + ACTOR( alice ); + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60*60; // too far in the future + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( !logged_in ); + +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE( fail_with_timestamp_too_old ) +{ try { + + ACTOR( alice ); + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() - 60*60; // too far in the past + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( !logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( fail_with_from_neq_to ) +{ try { + + ACTORS( (alice) (bob) ); + transfer_operation op; + op.from = alice_id; + op.to = bob_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; // too far in the past + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( !logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( fail_with_not_transfer_op_in_trx ) +{ try { + + ACTOR( alice ); + account_update_operation op; + op.account = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( !logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( fail_with_empty_signature_keys ) +{ try { + + ACTOR( alice ); + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( !logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( fail_with_wrong_signature ) +{ try { + + ACTORS( (alice) (bob) ); + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; + trx.sign( bob_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + login_api.enable_api( "database_api" ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( !logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( fail_as_default_user_no_lifetime_member ) +{ try { + + api_access_info_signed info; + info.required_lifetime_member = true; + info.required_registrar = ""; + info.allowed_apis = { "database_api" }; + + app.set_api_access_info_signed_default( {info} ); + + ACTOR( alice ); + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + login_api.enable_api( "database_api" ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( !logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( fail_as_default_user_no_required_registrar ) +{ try { + + api_access_info_signed info; + info.required_lifetime_member = false; + info.required_registrar = "required_registrar_name"; + info.allowed_apis = { "database_api" }; + + app.set_api_access_info_signed_default( {info} ); + + ACTOR( alice ); + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + login_api.enable_api( "database_api" ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( !logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( pass_as_default_user_no_specials ) +{ try { + + api_access_info_signed info; + info.required_lifetime_member = false; + info.required_registrar = ""; + info.allowed_apis = { "database_api" }; + + app.set_api_access_info_signed_default( {info} ); + + ACTOR( alice ); + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + login_api.enable_api( "database_api" ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( pass_as_default_user_with_lifetime_member ) +{ try { + + api_access_info_signed info; + info.required_lifetime_member = true; + info.required_registrar = ""; + info.allowed_apis = { "database_api" }; + + app.set_api_access_info_signed_default( {info} ); + + ACTOR( alice ); + db.modify( alice, [](account_object& obj) { + obj.membership_expiration_date = time_point_sec::maximum(); + }); + + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + login_api.enable_api( "database_api" ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( pass_as_special_user ) +{ try { + + api_access_info_signed info; + info.required_lifetime_member = false; + info.required_registrar = ""; + info.allowed_apis = { "database_api" }; + + app.set_api_access_info_signed_user( "alice", std::move(info) ); + + ACTOR( alice ); + transfer_operation op; + op.from = alice_id; + op.to = alice_id; + + signed_transaction trx; + trx.operations.push_back( op ); + trx.expiration = db.head_block_time() + 60; + trx.sign( alice_private_key, db.get_chain_id() ); + + auto json = fc::json::to_string( trx ); + auto encoded = fc::base64_encode( json ); + + login_api login_api( app ); + login_api.enable_api( "database_api" ); + bool logged_in = login_api.login_signed( encoded ); + BOOST_CHECK( logged_in ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END()