Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feature/api login - requesting feedback #1880

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions api_access.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
]
]
}
114 changes: 114 additions & 0 deletions libraries/app/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@
#include <graphene/chain/transaction_history_object.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/protocol/transaction.hpp>

#include <fc/crypto/hex.hpp>
#include <fc/rpc/api_connection.hpp>
#include <fc/thread/future.hpp>
#include <fc/crypto/pke.hpp>
#include <fc/time.hpp>
#include <fc/variant.hpp>

template class fc::api<graphene::app::block_api>;
template class fc::api<graphene::app::network_broadcast_api>;
Expand Down Expand Up @@ -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<signed_transaction>( 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<transfer_operation>::value ) // only transfer op for validation
return false;

const auto acc_id = op.get<transfer_operation>().from;
const auto to = op.get<transfer_operation>().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<api_access_info_signed>::value )
{
const auto& api_access_info = api_access_info_var->get<api_access_info_signed>();
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<std::vector<api_access_info_signed>>::value
{
const auto& api_access_info_vec = api_access_info_var->get<std::vector<api_access_info_signed>>();
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" )
Expand Down
71 changes: 57 additions & 14 deletions libraries/app/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> 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<std::string> 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()
Expand Down Expand Up @@ -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())
);
}
Expand Down Expand Up @@ -481,7 +489,7 @@ void application_impl::startup()

fc::path api_access_file = _options->at("api-access").as<boost::filesystem::path>();

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<api_access>( 20 );
Expand Down Expand Up @@ -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<api_access_info_signed>&& 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.
*/
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<api_access_info_signed>&& 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;
Expand Down
6 changes: 6 additions & 0 deletions libraries/app/application_impl.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -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<api_access_info_signed>&& 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.
*/
Expand Down
6 changes: 6 additions & 0 deletions libraries/app/include/graphene/app/api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_api> block()const;
/// @brief Retrieve the network broadcast API
Expand Down Expand Up @@ -582,6 +584,9 @@ namespace graphene { namespace app {
optional< fc::api<asset_api> > _asset_api;
optional< fc::api<orders_api> > _orders_api;
optional< fc::api<graphene::debug_witness::debug_api> > _debug_api;

bool verify_api_access_info_signed( const account_object& acc,
const api_access_info_signed& api_access_info );
};

}} // graphene::app
Expand Down Expand Up @@ -659,4 +664,5 @@ FC_API(graphene::app::login_api,
(asset)
(orders)
(debug)
(login_signed)
)
Loading