From 9501d73546344f0b93a7a947abd9438120018461 Mon Sep 17 00:00:00 2001 From: Kevin F Date: Fri, 14 Oct 2022 18:26:07 +0000 Subject: [PATCH] Adding WOT API to RPC to enable 3rd party apps like merkato --- static-data/official-plugins/rpc/main.py | 38 ++++++++++++++++--- .../official-plugins/rpc/rpc/addmodule.py | 11 ++++++ .../rpc/rpc/pluginrpcmethods.py | 6 ++- .../official-plugins/wot/cli/__init__.py | 32 +++++++++++++++- static-data/official-plugins/wot/main.py | 11 ++++-- .../official-plugins/wot/wot/__init__.py | 1 + .../official-plugins/wot/wot/getbykey.py | 2 + .../wot/wot/identity/identityset.py | 10 ++++- 8 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 static-data/official-plugins/rpc/rpc/addmodule.py diff --git a/static-data/official-plugins/rpc/main.py b/static-data/official-plugins/rpc/main.py index e58e84d9..7a4f61c8 100644 --- a/static-data/official-plugins/rpc/main.py +++ b/static-data/official-plugins/rpc/main.py @@ -5,8 +5,7 @@ Default example plugin for devs or to test blocks import sys import os import locale -from threading import Thread -from time import sleep +from secrets import randbelow import cherrypy @@ -14,8 +13,11 @@ locale.setlocale(locale.LC_ALL, '') sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) # import after path insert +from logger import log as logging from utils import identifyhome from onionrthreads import add_onionr_thread +import config +from onionrplugins.pluginapis import plugin_apis """ This program is free software: you can redistribute it and/or modify @@ -45,6 +47,9 @@ jsonrpc.manager.json = ujson # RPC modules map Onionr APIs to the RPC dispacher from rpc import blocks, pluginrpcmethods +from rpc.addmodule import add_module_to_api + +plugin_apis['rpc.add_module_to_api'] = add_module_to_api class OnionrRPC(object): @cherrypy.expose @@ -58,16 +63,37 @@ class OnionrRPC(object): def on_afterinit(api, data=None): + def ping(): + return "pong" + dispatcher['ping'] = ping pluginrpcmethods.add_plugin_rpc_methods() +def _gen_random_loopback(): + return f'127.{randbelow(256)}.{randbelow(256)}.{randbelow(256)}' + + def on_init(api, data=None): - config = { - #'server.socket_file': socket_file_path, - 'server.socket_port': 0, + bind_config = {} + if config.get('rpc.use_sock_file', True, save=True): + bind_config['server.socket_file'] = config.get( + 'rpc.sock_file_path', socket_file_path, save=True) + # create base dir if it doesn't exist + os.makedirs( + os.path.dirname(config.get('rpc.sock_file_path', socket_file_path)), exist_ok=True) + else: + # Set default bind TCP address, if not set + # We use a random loopback address to avoid browser side channel attacks + # and let the OS pick a port (0) + bind_config['server.socket_host'] = config.get( + 'rpc.bind_host', _gen_random_loopback(), save=True) + bind_config['server.socket_port'] = config.get('rpc.bind_port', 0) + cherrpy_config = bind_config | { 'engine.autoreload.on': False } - cherrypy.config.update(config) + cherrypy.config.update(cherrpy_config) + + logging.info("Starting RPC Server") add_onionr_thread( cherrypy.quickstart, 5, 'OnionrRPCServer', diff --git a/static-data/official-plugins/rpc/rpc/addmodule.py b/static-data/official-plugins/rpc/rpc/addmodule.py new file mode 100644 index 00000000..0f75f219 --- /dev/null +++ b/static-data/official-plugins/rpc/rpc/addmodule.py @@ -0,0 +1,11 @@ +from types import ModuleType +from jsonrpc import dispatcher + + +def add_module_to_api(module: ModuleType): + prefix = f"{module.__name__}." + for attr in dir(module): + attr = getattr(module, attr) + if callable(attr): + if hasattr(attr, 'json_compatible'): + dispatcher[prefix + attr.__name__] = attr \ No newline at end of file diff --git a/static-data/official-plugins/rpc/rpc/pluginrpcmethods.py b/static-data/official-plugins/rpc/rpc/pluginrpcmethods.py index 7719f1e4..ea742296 100644 --- a/static-data/official-plugins/rpc/rpc/pluginrpcmethods.py +++ b/static-data/official-plugins/rpc/rpc/pluginrpcmethods.py @@ -5,4 +5,8 @@ from onionrplugins.pluginapis import plugin_apis def add_plugin_rpc_methods(): for method in plugin_apis: - dispatcher[method] = plugin_apis[method] \ No newline at end of file + try: + if plugin_apis[method].json_compatible: + dispatcher[method] = plugin_apis[method] + except AttributeError: + pass \ No newline at end of file diff --git a/static-data/official-plugins/wot/cli/__init__.py b/static-data/official-plugins/wot/cli/__init__.py index a93def53..c61926d3 100644 --- a/static-data/official-plugins/wot/cli/__init__.py +++ b/static-data/official-plugins/wot/cli/__init__.py @@ -1 +1,31 @@ -import tty \ No newline at end of file +import tty +import sys +import subprocess + +def do_quit(): raise KeyboardInterrupt + +def list_idens(): + print('Listing identities') + + +main_menu = { + 'l': (list_idens, 'list identities'), + 'q': (do_quit, 'quit CLI') +} + +def main_ui(): + tty.setraw(sys.stdin) + + while True: + # move cursor to the beginning + print('\r', end='') + key = sys.stdin.read(1) + try: + main_menu[key][1]() + except KeyError: + pass + except KeyboardInterrupt: + break + + + subprocess.Popen(['reset'], stdout=subprocess.PIPE) diff --git a/static-data/official-plugins/wot/main.py b/static-data/official-plugins/wot/main.py index 5faa9ca1..ae035cf7 100644 --- a/static-data/official-plugins/wot/main.py +++ b/static-data/official-plugins/wot/main.py @@ -37,15 +37,16 @@ along with this program. If not, see . plugin_name = 'wot' PLUGIN_VERSION = '0.0.1' from wot.identity import identities +from cli import main_ui +from onionrplugins import plugin_apis + +import wot from wot.loadfromblocks import load_identities_from_blocks -from wot.identity import get_distance def on_init(api, data=None): logging.info( f"Web of Trust Plugin v{PLUGIN_VERSION} enabled") - #onionrplugins.plugin_apis['wot'] = wot_test - plugin_apis['wot.get_distance'] = get_distance list( map( @@ -53,6 +54,8 @@ def on_init(api, data=None): load_identities_from_blocks()) ) + plugin_apis['rpc.add_module_to_api'](wot) + def on_wot_cmd(api, data=None): - return + main_ui() diff --git a/static-data/official-plugins/wot/wot/__init__.py b/static-data/official-plugins/wot/wot/__init__.py index ea325bbe..724d7384 100644 --- a/static-data/official-plugins/wot/wot/__init__.py +++ b/static-data/official-plugins/wot/wot/__init__.py @@ -5,4 +5,5 @@ from typing import TYPE_CHECKING, Set from .identity import Identity from .getbykey import get_identity_by_key from .identity import identities +from .identity.identityset import serialize_identity_set diff --git a/static-data/official-plugins/wot/wot/getbykey.py b/static-data/official-plugins/wot/wot/getbykey.py index 5d87d5f5..940ed081 100644 --- a/static-data/official-plugins/wot/wot/getbykey.py +++ b/static-data/official-plugins/wot/wot/getbykey.py @@ -17,3 +17,5 @@ def get_identity_by_key( if bytes(identity.key) == bytes(key): return identity raise KeyError("Identity not found") + +get_identity_by_key \ No newline at end of file diff --git a/static-data/official-plugins/wot/wot/identity/identityset.py b/static-data/official-plugins/wot/wot/identity/identityset.py index e042bdbc..b2a1cf45 100644 --- a/static-data/official-plugins/wot/wot/identity/identityset.py +++ b/static-data/official-plugins/wot/wot/identity/identityset.py @@ -27,4 +27,12 @@ class IdentitySet(set): # Set of identites within N-distance trust -identities = IdentitySet() \ No newline at end of file +identities = IdentitySet() + +def serialize_identity_set(): + serialized_idens = [] + for identity in list(identities): + serialized_idens.append(identity.serialize()) + return serialized_idens + +serialize_identity_set.json_compatible = True \ No newline at end of file