Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
revflash committed May 26, 2016
2 parents 5debb51 + f93f33d commit 153cc0b
Show file tree
Hide file tree
Showing 71 changed files with 3,662 additions and 5,509 deletions.
57 changes: 35 additions & 22 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,36 +88,49 @@ ENDIF()

if( WIN32 )

message( STATUS "Configuring Steem on WIN32")
set( DB_VERSION 60 )
set( BDB_STATIC_LIBS 1 )
message( STATUS "Configuring Steem on WIN32")
set( DB_VERSION 60 )
set( BDB_STATIC_LIBS 1 )

set( ZLIB_LIBRARIES "" )
SET( DEFAULT_EXECUTABLE_INSTALL_DIR bin/ )
set( ZLIB_LIBRARIES "" )
SET( DEFAULT_EXECUTABLE_INSTALL_DIR bin/ )

set(CRYPTO_LIB)
set(CRYPTO_LIB)

#looks like this flag can have different default on some machines.
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SAFESEH:NO")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO")
if( MSVC )

# Probably cmake has a bug and vcxproj generated for executable in Debug conf. has disabled debug info
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /DEBUG")
#looks like this flag can have different default on some machines.
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SAFESEH:NO")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO")

# On windows tcl should be installed to the directory pointed by setenv.bat script
SET(TCL_INCLUDE_PATH $ENV{TCL_ROOT}/include)
MESSAGE(STATUS "tcl INCLUDE PATH: ${TCL_INCLUDE_PATH}")
# Probably cmake has a bug and vcxproj generated for executable in Debug conf. has disabled debug info
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /DEBUG")

FIND_PACKAGE(TCL)
MESSAGE(STATUS "tcl_library: ${TCL_LIBRARY}")
# On windows tcl should be installed to the directory pointed by setenv.bat script
SET(TCL_INCLUDE_PATH $ENV{TCL_ROOT}/include)
MESSAGE(STATUS "tcl INCLUDE PATH: ${TCL_INCLUDE_PATH}")

SET(TCL_LIBS "optimized;${TCL_LIBRARY};debug;")
get_filename_component(TCL_LIB_PATH "${TCL_LIBRARY}" PATH)
get_filename_component(TCL_LIB_NAME "${TCL_LIBRARY}" NAME_WE)
get_filename_component(TCL_LIB_EXT "${TCL_LIBRARY}" EXT)
FIND_PACKAGE(TCL)
MESSAGE(STATUS "tcl_library: ${TCL_LIBRARY}")

SET(TCL_LIBS "${TCL_LIBS}${TCL_LIB_PATH}/${TCL_LIB_NAME}g${TCL_LIB_EXT}")
SET(TCL_LIBRARY ${TCL_LIBS})
SET(TCL_LIBS "optimized;${TCL_LIBRARY};debug;")
get_filename_component(TCL_LIB_PATH "${TCL_LIBRARY}" PATH)
get_filename_component(TCL_LIB_NAME "${TCL_LIBRARY}" NAME_WE)
get_filename_component(TCL_LIB_EXT "${TCL_LIBRARY}" EXT)

SET(TCL_LIBS "${TCL_LIBS}${TCL_LIB_PATH}/${TCL_LIB_NAME}g${TCL_LIB_EXT}")
SET(TCL_LIBRARY ${TCL_LIBS})

elseif( MINGW )
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -msse4.2 -Wa,-mbig-obj")
SET(CMAKE_CXX_FLAGS_RELEASE "-O3")
# Optimization flag apparently needed to get rid of "File too big" assembler errors when compiling in Debug mode
# See: https://stackoverflow.com/questions/14125007/gcc-string-table-overflow-error-during-compilation/14601779#29479701
SET(CMAKE_CXX_FLAGS_DEBUG "-O2")
if ( FULL_STATIC_BUILD )
set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++ -static-libgcc")
endif ( FULL_STATIC_BUILD )
endif( MSVC )

else( WIN32 ) # Apple AND Linux

Expand Down
120 changes: 120 additions & 0 deletions doc/api-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@

What is an API
--------------

An API is a set of related methods, accessible over Websocket / HTTP and provided by a single C++ class. API's exist on a per-connection basis.

Compiling with hello_api plugin
-------------------------------

We will use the `hello_api` example plugin. To follow along with the examples, you may enable the `hello_api` example plugin by running the following command and recompiling:

ln -s ../example_plugins/hello_api external_plugins/hello_api

Publicly available API's
------------------------

Some API's are public API's which are available to clients without login. These API's may be specified as follows in the configuration file:

public-api = database_api login_api hello_api_api

Note, `hello_api` is the name of the plugin and `hello_api_api` is the name of the API it provides. This slightly confusing naming may be revised in a later version of the plugin.

You may also specify `--public-api` on the command line.

Server configuration note: If you customize `public-api` configuration, you should set the first two API's to `database_api` and `login_api`.

For experts: The reason for recommending `database_api` and `login_api` to be the first two API's is that many clients expect these to be first two API's.
Additionally, the string API identifier feature's FC implementation requires a working `get_api_by_name` method to be available on API ID 1, so effectively
any client that uses string API identifiers (as recommended) requires `login_api` to be on API ID 1 even if no client uses the login feature.

Numeric API identifiers
-----------------------

API's are assigned numeric ID's in the order they are specified as public API's. So for example you can access `hello_api_api` over HTTP like this:

curl --data '{"jsonrpc": "2.0", "params": [2, "get_message", []], "id":1, "method":"call"}' http://127.0.0.1:8090/rpc

The number `2` is the numeric API identifier for `hello_api_api`. These identifiers are assigned sequentially starting with 0 when the API is registered to the connection.
So effectively this means numeric API identifiers are determined by the order of `hello_api_api` in the `public-api` declaration above.

The `get_api_by_name` method on API 1 (the `login_api`) can be used to query the numeric ID by name:

curl --data '{"jsonrpc": "2.0", "params": [1, "get_api_by_name", ["hello_api_api"]], "id":1, "method":"call"}' http://127.0.0.1:8790/rpc

String API identifiers
----------------------

Client code that references API's by their numeric identifiers have to coordinated with server-side configuration.
This is inconvenient, so the API server supports identifying the API by name as well, like this:

curl --data '{"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":1, "method":"call"}' http://127.0.0.1:8090/rpc

It is considered best practice for API clients to use this syntax to reference API's by their string identifiers. The reason is that the client becomes robust against
server-side configuration changes which result in renumbering of API's.

API access control methods
--------------------------

There are three methods to secure the API:

- Limit access to the API socket to a trusted machine by binding the RPC server to localhost
- Limit access to the API socket to a trusted LAN by firewall configuration
- Limit access to particular API's with username/password authentication

The Steem developers recommend using the first of these methods to secure the API by binding to localhost, as follows:

rpc-endpoint = 127.0.0.1:8090

Securing specific API's
-----------------------

The problem with securing API's at the network level is that there are deployment scenarios where a node may want to have some API's public, but other API's private.
The `steemd` process includes username/password based authentication to individual API's.

Since the username/password is sent directly over the wire, you should use a TLS connection when authenticating with username and password. TLS connection can be achieved by one of two methods:

- Configure an `rpc-tls-endpoint`.
- Configure an `rpc-endpoint` bound to localhost (or a secure LAN), and use an external reverse proxy (for example, `nginx`). This advanced configuration will not be covered here.

You will need to create a key and certificate for the server in `.pem` format. Many tutorials for creating SSL certificates exist, and many providers allow creating SSL
certificates signed by widely recognized CA's. If you have a working `openssl` installation you can use these commands to generate a self-signed certificate:

openssl req -x509 -newkey rsa:4096 -keyout data/ss-key.pem -out data/ss-cert.pem -days 365
cat data/ss-{cert,key}.pem > data/ss-cert-key.pem

Then in your `config.ini` you must give the server access to the key and certificate:

rpc-tls-endpoint = 0.0.0.0:8091
server-pem = data/ss-cert-key.pem
server-pem-password = password

When connecting to the API, you can use the `cli_wallet` to honor a specific certificate, individual CA, or CA bundle file with the `-a` option:

programs/cli_wallet/cli_wallet -s wss://mydomain.tld:8091 -a data/ss-cert.pem

Note that the hostname `mydomain.tld` needs to match the CN field in the certificate.

Now let's protect the `hello_api_api` API to only be usable by user `bytemaster` with password `supersecret`. Modify the `config.ini` file to make
sure we have the correct plugin which provides the sensitive API, make sure the sensitive API is not accessible in `public-api`, and add an
`api-user` definition to define the access policy:

enable-plugin = witness account_history hello_api
public-api = database_api login_api
api-user = {"username" : "bytemaster", "password_hash_b64" : "T2k/wMBB9BKyv7X+ngghL+MaoubEuFb6GWvF3qQ9NU0=", "password_salt_b64" : "HqK9mAQCkWU=", "allowed_apis" : ["hello_api_api"]}

The values of `password_hash_b64` and `password_salt_b64` are generated by running `programs/util/saltpass.py` and entering your desired password.

Username/password access is only available through websockets, since login is inherently *stateful*. We can use the `wscat` program to demonstrate the login functionality:

wscat -c wss://mydomain.tld:8091
{"jsonrpc": "2.0", "params": ["database_api", "get_dynamic_global_properties", []], "id":1, "method":"call"}
< (ok)
{"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":2, "method":"call"}
< (error)
{"jsonrpc": "2.0", "params": ["login_api", "login", ["bytemaster", "supersecret"]], "id":3, "method":"call"}
< (ok)
{"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":4, "method":"call"}
< (ok)

The `cli_wallet` also has the capability to login to provide access to restricted API's.
27 changes: 27 additions & 0 deletions doc/plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

How plugins work
----------------

All plugins in the `libraries/plugins` directory are iterated over by `CMakeLists.txt` and placed in a CMake environment variable `STEEMIT_INTERNAL_PLUGINS`, which is used to create a runtime-accessible list of available plugins used by the argument parsing.

Similarly, `external_plugins` is set aside for third-party plugins. Just drop plugin code into `external_plugins` directory, `make steemd`, and the new plugin will be available.

There is a plugin in `example_plugins` called `hello_api` which is a working example of adding a custom API call.

Registering plugins
-------------------

- Plugins are enabled with the `enable-plugin` config file option.
- When specifying plugins, you should specify `witness` and `account_history` in addition to the new plugins.
- Some plugins may keep records in the database (currently only `account_history` does). If you change whether such a plugin is disabled/enabled, you should also replay the chain. Detecting this situation and automatically replaying when needed will be implemented in a future release.
- If you want to make API's available publicly, you must use the `public-api` option.
- When specifying public API's, you should specify `database_api` and `login_api` in addition to the new plugins.
- The `api-user` option allows for password protected access to an API.

Autogenerating code
-------------------

A skeleton structure containing much of the boilerplate of creating a new plugin with an API can be autogenerated by running `programs/util/newplugin.py`.

- Register signal handlers in `plugin_startup()` (if needed)
- Add methods to `myplugin_api` class and reflect them in `FC_API` declaration
4 changes: 2 additions & 2 deletions example_plugins/hello_api/hello_api_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class hello_api_plugin : public steemit::app::plugin
class hello_api_api
{
public:
hello_api_api( steemit::app::application& app );
hello_api_api( const steemit::app::api_context& ctx );

/**
* Called immediately after the constructor. If the API class uses enable_shared_from_this,
Expand Down Expand Up @@ -96,7 +96,7 @@ std::string hello_api_plugin::get_message()
return result.str();
}

hello_api_api::hello_api_api( steemit::app::application& app ) : _app(app) {}
hello_api_api::hello_api_api( const steemit::app::api_context& ctx ) : _app(ctx.app) {}

void hello_api_api::on_api_startup() {}

Expand Down
13 changes: 0 additions & 13 deletions libraries/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@

## This feature depends upon Qt 5.5 but is not required for consensus
IF( ENABLE_CONTENT_PATCHING )
find_package(Qt5Core REQUIRED)
MESSAGE( STATUS " CONFIGURING WITH DIFF MATCH PATCH " )
SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTEEM_DIFF_MATCH_PATCH" )
add_subdirectory( google-diff-match-patch/cpp )

SET( PATCH_MERGE_LIB google_patch_diff_merge Qt5::Core)
ELSE()
MESSAGE( STATUS " CONFIGURING WITHOUT DIFF MATCH PATCH " )
ENDIF( ENABLE_CONTENT_PATCHING )

add_subdirectory( fc )
add_subdirectory( db )
add_subdirectory( deterministic_openssl_rand )
Expand Down
2 changes: 1 addition & 1 deletion libraries/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ add_library( steemit_app
${HEADERS}
)

target_link_libraries( steemit_app steemit_chain steemit_mf_plugins fc graphene_db graphene_net graphene_time graphene_utilities )
target_link_libraries( steemit_app steemit_chain steemit_tags steemit_mf_plugins fc graphene_db graphene_net graphene_time graphene_utilities )
target_include_directories( steemit_app
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )

Expand Down
32 changes: 26 additions & 6 deletions libraries/app/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@

namespace steemit { namespace app {

login_api::login_api(application& a)
:_app(a)
login_api::login_api(const api_context& ctx)
:_ctx(ctx)
{
}

Expand All @@ -54,7 +54,7 @@ namespace steemit { namespace app {
bool login_api::login(const string& user, const string& password)
{
idump((user)(password));
optional< api_access_info > acc = _app.get_api_access_info( user );
optional< api_access_info > acc = _ctx.app.get_api_access_info( user );
if( !acc.valid() )
return false;
if( acc->password_hash_b64 != "*" )
Expand All @@ -70,6 +70,8 @@ namespace steemit { namespace app {
}

idump((acc->allowed_apis));
std::map< std::string, api_ptr >& _api_map = _ctx.connection->api_map;

for( const std::string& api_name : acc->allowed_apis )
{
auto it = _api_map.find( api_name );
Expand All @@ -78,12 +80,30 @@ namespace steemit { namespace app {
continue;
}
idump((api_name));
_api_map[ api_name ] = _app.create_api_by_name( api_name );
api_context new_ctx( _ctx.app, api_name, _ctx.connection );
_api_map[ api_name ] = _ctx.app.create_api_by_name( new_ctx );
}
return true;
}

network_broadcast_api::network_broadcast_api(application& a):_app(a)
fc::api_ptr login_api::get_api_by_name( const string& api_name )const
{
const std::map< std::string, api_ptr >& _api_map = _ctx.connection->api_map;
auto it = _api_map.find( api_name );
if( it == _api_map.end() )
{
wlog( "unknown api: ${api}", ("api",api_name) );
return fc::api_ptr();
}
if( it->second )
{
ilog( "found api: ${api}", ("api",api_name) );
}
FC_ASSERT( it->second != nullptr );
return it->second;
}

network_broadcast_api::network_broadcast_api(const api_context& a):_app(a.app)
{
/// NOTE: cannot register callbacks in constructor because shared_from_this() is not valid.
}
Expand Down Expand Up @@ -167,7 +187,7 @@ namespace steemit { namespace app {
_app.p2p_node()->broadcast_transaction(trx);
}

network_node_api::network_node_api( application& a ) : _app( a )
network_node_api::network_node_api( const api_context& a ) : _app( a.app )
{
}

Expand Down
Loading

0 comments on commit 153cc0b

Please sign in to comment.