From 4d79a7828c7c8bcc77d5fb1462565861eb2ac5ea Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 21 Sep 2016 21:36:06 -0400 Subject: [PATCH] more cleaning up --- lbrynet/conf.py | 17 +- lbrynet/core/Error.py | 3 + lbrynet/lbrynet_daemon/LBRYDaemon.py | 130 +++++------ lbrynet/lbrynet_daemon/auth/auth.py | 2 +- lbrynet/lbrynet_daemon/auth/client.py | 19 +- lbrynet/lbrynet_daemon/auth/server.py | 319 ++++++++++++++++---------- lbrynet/lbrynet_daemon/auth/util.py | 8 +- 7 files changed, 276 insertions(+), 222 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 26ef18e3b..4989ddd77 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -45,12 +45,9 @@ KNOWN_DHT_NODES = [('104.236.42.182', 4000), POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' -if IS_DEVELOPMENT_VERSION: - SEARCH_SERVERS = ["http://107.170.207.64:50005"] -else: - SEARCH_SERVERS = ["http://lighthouse1.lbry.io:50005", - "http://lighthouse2.lbry.io:50005", - "http://lighthouse3.lbry.io:50005"] +SEARCH_SERVERS = ["http://lighthouse1.lbry.io:50005", + "http://lighthouse2.lbry.io:50005", + "http://lighthouse3.lbry.io:50005"] REFLECTOR_SERVERS = [("reflector.lbry.io", 5566)] @@ -67,6 +64,9 @@ if os.name == "nt": else: ICON_PATH = "app.icns" APP_NAME = "LBRY" + +ORIGIN = "http://%s:%i" % (API_INTERFACE, API_PORT) +REFERER = "http://%s:%i/" % (API_INTERFACE, API_PORT) API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRESS) UI_ADDRESS = "http://%s:%i" % (API_INTERFACE, API_PORT) PROTOCOL_PREFIX = "lbry" @@ -88,11 +88,6 @@ CURRENCIES = { 'USD': {'type': 'fiat'}, } -ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', - 'get_time_behind_blockchain', 'stop', - 'daemon_status', 'get_start_notice', - 'version', 'get_search_servers'] - LOGGLY_TOKEN = 'LJEzATH4AzRgAwxjAP00LwZ2YGx3MwVgZTMuBQZ3MQuxLmOv' ANALYTICS_ENDPOINT = 'https://api.segment.io/v1' diff --git a/lbrynet/core/Error.py b/lbrynet/core/Error.py index 363dddb3d..1f8355cea 100644 --- a/lbrynet/core/Error.py +++ b/lbrynet/core/Error.py @@ -93,4 +93,7 @@ class InvalidHeaderError(Exception): pass class InvalidAuthenticationToken(Exception): + pass + +class SubhandlerError(Exception): pass \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index f72cef38a..e5265264d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -40,7 +40,8 @@ from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager from lbrynet.lbrynet_daemon.Lighthouse import LighthouseClient -from lbrynet.lbrynet_daemon.auth.server import LBRYJSONRPCServer, auth_required, authorizer +from lbrynet.lbrynet_daemon.auth.server import AuthJSONRPCServer +from lbrynet.lbrynet_daemon.auth.util import get_auth_message from lbrynet.metadata.LBRYMetadata import Metadata, verify_name_characters from lbrynet.core import log_support from lbrynet.core import utils @@ -49,7 +50,7 @@ from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, \ KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, DEFAULT_WALLET, \ DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, \ - LOG_POST_URL, LOG_FILE_NAME, REFLECTOR_SERVERS, SEARCH_SERVERS, ALLOWED_DURING_STARTUP + LOG_POST_URL, LOG_FILE_NAME, REFLECTOR_SERVERS, SEARCH_SERVERS from lbrynet.conf import DEFAULT_SD_DOWNLOAD_TIMEOUT from lbrynet.conf import DEFAULT_TIMEOUT from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob, BlobStreamDescriptorReader @@ -122,10 +123,8 @@ BAD_REQUEST = 400 NOT_FOUND = 404 OK_CODE = 200 -# TODO add login credentials in a conf file # TODO alert if your copy of a lbry file is out of date with the name record - REMOTE_SERVER = "www.google.com" @@ -134,14 +133,17 @@ class Parameters(object): self.__dict__.update(kwargs) -@authorizer -class LBRYDaemon(LBRYJSONRPCServer): +class LBRYDaemon(AuthJSONRPCServer): """ LBRYnet daemon, a jsonrpc interface to lbry functions """ def __init__(self, root, wallet_type=None): - LBRYJSONRPCServer.__init__(self) + AuthJSONRPCServer.__init__(self) + self.allowed_during_startup = ['is_running', 'is_first_run', + 'get_time_behind_blockchain', 'stop', + 'daemon_status', 'get_start_notice', + 'version', 'get_search_servers'] reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) self.startup_status = STARTUP_STAGES[0] @@ -392,6 +394,17 @@ class LBRYDaemon(LBRYJSONRPCServer): f.write("rpcpassword=" + password) log.info("Done writing lbrycrd.conf") + @AuthJSONRPCServer.subhandler + def _exclude_lbrycrd_only_commands_from_lbryum_session(self, request): + request.content.seek(0, 0) + content = request.content.read() + parsed = jsonrpclib.loads(content) + function_path = parsed.get("method") + if self.wallet_type == "lbryum" and function_path in ['set_miner', 'get_miner_status']: + log.warning("Mining commands are not available in lbryum") + raise Exception("Command not available in lbryum") + return True + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, host_ui=True): def _log_starting_vals(): log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) @@ -1337,7 +1350,7 @@ class LBRYDaemon(LBRYJSONRPCServer): def _search(self, search): return self.lighthouse_client.search(search) - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_is_running(self): """ Check if lbrynet daemon is running @@ -1354,7 +1367,6 @@ class LBRYDaemon(LBRYJSONRPCServer): else: return self._render_response(False, OK_CODE) - @auth_required def jsonrpc_daemon_status(self): """ Get lbrynet daemon status information @@ -1386,10 +1398,8 @@ class LBRYDaemon(LBRYJSONRPCServer): else: r['message'] = "Catching up with the blockchain" r['progress'] = 0 - log.info("daemon status: " + str(r)) return self._render_response(r, OK_CODE) - @auth_required def jsonrpc_is_first_run(self): """ Check if this is the first time lbrynet daemon has been run @@ -1410,7 +1420,6 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required def jsonrpc_get_start_notice(self): """ Get special message to be displayed at startup @@ -1430,7 +1439,6 @@ class LBRYDaemon(LBRYJSONRPCServer): else: self._render_response(self.startup_message, OK_CODE) - @auth_required def jsonrpc_version(self): """ Get lbry version information @@ -1465,7 +1473,7 @@ class LBRYDaemon(LBRYJSONRPCServer): log.info("Get version info: " + json.dumps(msg)) return self._render_response(msg, OK_CODE) - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_settings(self): """ Get lbrynet daemon settings @@ -1494,7 +1502,7 @@ class LBRYDaemon(LBRYJSONRPCServer): log.info("Get daemon settings") return self._render_response(self.session_settings, OK_CODE) - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_set_settings(self, p): """ Set lbrynet daemon settings @@ -1522,7 +1530,6 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required def jsonrpc_help(self, p=None): """ Function to retrieve docstring for API function @@ -1537,17 +1544,17 @@ class LBRYDaemon(LBRYJSONRPCServer): """ if not p: - return self._render_response(self._listFunctions(), OK_CODE) + return self._render_response(self.callable_methods.keys(), OK_CODE) elif 'callable_during_start' in p.keys(): - return self._render_response(ALLOWED_DURING_STARTUP, OK_CODE) + return self._render_response(self.allowed_during_startup, OK_CODE) elif 'function' in p.keys(): func_path = p['function'] - function = self._getFunction(func_path) + function = self.callable_methods.get(func_path) return self._render_response(function.__doc__, OK_CODE) else: return self._render_response(self.jsonrpc_help.__doc__, OK_CODE) - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_balance(self): """ Get balance @@ -1561,7 +1568,6 @@ class LBRYDaemon(LBRYJSONRPCServer): log.info("Get balance") return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE) - @auth_required def jsonrpc_stop(self): """ Stop lbrynet-daemon @@ -1581,7 +1587,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return self._render_response("Shutting down", OK_CODE) - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_lbry_files(self): """ Get LBRY files @@ -1608,7 +1614,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_lbry_file(self, p): """ Get lbry file @@ -1638,7 +1644,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required def jsonrpc_resolve_name(self, p): """ Resolve stream info from a LBRY uri @@ -1660,7 +1665,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure) return d - @auth_required def jsonrpc_get_claim_info(self, p): """ Resolve claim info from a LBRY uri @@ -1685,7 +1689,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def _process_get_parameters(self, p): """Extract info from input parameters and fill in default values for `get` call.""" # TODO: this process can be abstracted s.t. each method @@ -1707,7 +1711,7 @@ class LBRYDaemon(LBRYJSONRPCServer): name=name ) - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get(self, p): """Download stream from a LBRY uri. @@ -1738,7 +1742,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda message: self._render_response(message, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_stop_lbry_file(self, p): """ Stop lbry file @@ -1764,7 +1768,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_start_lbry_file(self, p): """ Stop lbry file @@ -1789,7 +1793,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required def jsonrpc_get_est_cost(self, p): """ Get estimated cost for a lbry uri @@ -1811,7 +1814,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_search_nametrie(self, p): """ Search the nametrie for claims @@ -1848,7 +1851,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_delete_lbry_file(self, p): """ Delete a lbry file @@ -1878,7 +1881,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_publish(self, p): """ Make a new name claim and publish associated data to lbrynet @@ -1955,7 +1958,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_abandon_claim(self, p): """ Abandon a name and reclaim credits from the claim @@ -1982,7 +1985,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_abandon_name(self, p): """ DEPRECIATED, use abandon_claim @@ -1995,7 +1998,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return self.jsonrpc_abandon_claim(p) - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_support_claim(self, p): """ Support a name claim @@ -2015,7 +2018,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_name_claims(self): """ Get my name claims @@ -2039,7 +2042,6 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required def jsonrpc_get_claims_for_name(self, p): """ Get claims for a name @@ -2055,7 +2057,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_transaction_history(self): """ Get transaction history @@ -2070,7 +2072,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required def jsonrpc_get_transaction(self, p): """ Get a decoded transaction from a txid @@ -2087,7 +2088,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_address_is_mine(self, p): """ Checks if an address is associated with the current wallet. @@ -2105,7 +2106,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_public_key_from_wallet(self, p): """ Get public key from wallet address @@ -2120,7 +2121,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d = self.session.wallet.get_pub_keys(wallet) d.addCallback(lambda r: self._render_response(r, OK_CODE)) - @auth_required def jsonrpc_get_time_behind_blockchain(self): """ Get number of blocks behind the blockchain @@ -2144,7 +2144,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_new_address(self): """ Generate a new wallet address @@ -2164,7 +2164,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda address: self._render_response(address, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_send_amount_to_address(self, p): """ Send credits to an address @@ -2189,7 +2189,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d - @auth_required def jsonrpc_get_best_blockhash(self): """ Get hash of most recent block @@ -2204,7 +2203,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required def jsonrpc_get_block(self, p): """ Get contents of a block @@ -2227,7 +2225,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required def jsonrpc_get_claims_for_tx(self, p): """ Get claims for tx @@ -2247,7 +2244,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_download_descriptor(self, p): """ Download and return a sd blob @@ -2264,7 +2261,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: self._render_response(False, OK_CODE)) return d - @auth_required def jsonrpc_get_nametrie(self): """ Get the nametrie @@ -2280,7 +2276,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_set_miner(self, p): """ Start of stop the miner, function only available when lbrycrd is set as the wallet @@ -2300,7 +2296,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_miner_status(self): """ Get status of miner @@ -2315,7 +2311,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required def jsonrpc_log(self, p): """ Log message @@ -2330,7 +2325,6 @@ class LBRYDaemon(LBRYJSONRPCServer): log.info("API client log request: %s" % message) return self._render_response(True, OK_CODE) - @auth_required def jsonrpc_upload_log(self, p=None): """ Upload log @@ -2372,7 +2366,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_configure_ui(self, p): """ Configure the UI being hosted @@ -2397,7 +2391,7 @@ class LBRYDaemon(LBRYJSONRPCServer): return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_reveal(self, p): """ Reveal a file or directory in file browser @@ -2417,7 +2411,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_peers_for_hash(self, p): """ Get peers for blob hash @@ -2435,7 +2429,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_announce_all_blobs_to_dht(self): """ Announce all blobs to the dht @@ -2450,7 +2444,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda _: self._render_response("Announced", OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_reflect(self, p): """ Reflect a stream @@ -2467,7 +2461,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallbacks(lambda _: self._render_response(True, OK_CODE), lambda err: self._render_response(err.getTraceback(), OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_get_blob_hashes(self): """ Returns all blob hashes @@ -2482,7 +2476,7 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required + @AuthJSONRPCServer.auth_required def jsonrpc_reflect_all_blobs(self): """ Reflects all saved blobs @@ -2498,20 +2492,6 @@ class LBRYDaemon(LBRYJSONRPCServer): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - @auth_required - def jsonrpc_get_search_servers(self): - """ - Get list of lighthouse servers - - Args: - None - Returns: - List of address:port - """ - - d = self._render_response(SEARCH_SERVERS, OK_CODE) - return d - def get_lbrynet_version_from_github(): """Return the latest released version from github.""" diff --git a/lbrynet/lbrynet_daemon/auth/auth.py b/lbrynet/lbrynet_daemon/auth/auth.py index f68772fe2..bcda95829 100644 --- a/lbrynet/lbrynet_daemon/auth/auth.py +++ b/lbrynet/lbrynet_daemon/auth/auth.py @@ -14,7 +14,7 @@ class HttpPasswordRealm(object): self.resource = resource def requestAvatar(self, avatarId, mind, *interfaces): - log.info("Processing request for %s", avatarId) + log.debug("Processing request for %s", avatarId) if resource.IResource in interfaces: return (resource.IResource, self.resource, lambda: None) raise NotImplementedError() diff --git a/lbrynet/lbrynet_daemon/auth/client.py b/lbrynet/lbrynet_daemon/auth/client.py index 31f62949c..2e479726b 100644 --- a/lbrynet/lbrynet_daemon/auth/client.py +++ b/lbrynet/lbrynet_daemon/auth/client.py @@ -5,7 +5,7 @@ import os import base64 import json -from lbrynet.lbrynet_daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME +from lbrynet.lbrynet_daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME, get_auth_message from lbrynet.conf import API_INTERFACE, API_ADDRESS, API_PORT from lbrynet.conf import DATA_DIR @@ -55,8 +55,8 @@ class LBRYAPIClient(object): 'method': self.__service_name, 'params': args, 'id': self.__id_count} - to_auth = str(pre_auth_postdata['method']).encode('hex') + str(pre_auth_postdata['id']).encode('hex') - token = self.__api_key.get_hmac(to_auth.decode('hex')) + to_auth = get_auth_message(pre_auth_postdata) + token = self.__api_key.get_hmac(to_auth) pre_auth_postdata.update({'hmac': token}) postdata = json.dumps(pre_auth_postdata) service_url = self.__service_url @@ -79,16 +79,13 @@ class LBRYAPIClient(object): next_secret = headers.get(LBRY_SECRET, False) if next_secret: - # print "Next secret: %s" % next_secret self.__api_key.secret = next_secret self.__cookies = cookies - # print "Postdata: %s" % postdata if http_response is None: raise JSONRPCException({ 'code': -342, 'message': 'missing HTTP response from server'}) - # print "-----\n%s\n------" % http_response.text http_response.raise_for_status() response = http_response.json() @@ -104,20 +101,18 @@ class LBRYAPIClient(object): @classmethod def config(cls, key_name=None, key=None, pw_path=None, timeout=HTTP_TIMEOUT, connection=None, count=0, service=None, cookies=None, auth=None, url=None, login_url=None): + api_key_name = API_KEY_NAME if not key_name else key_name pw_path = os.path.join(DATA_DIR, ".api_keys") if not pw_path else pw_path - if not key: keys = load_api_keys(pw_path) api_key = keys.get(api_key_name, False) else: api_key = APIKey(name=api_key_name, secret=key) - if login_url is None: service_url = "http://%s:%s@%s:%i/%s" % (api_key_name, api_key.secret, API_INTERFACE, API_PORT, API_ADDRESS) else: service_url = login_url - id_count = count if auth is None and connection is None and cookies is None and url is None: @@ -134,10 +129,8 @@ class LBRYAPIClient(object): pass authpair = user + b':' + passwd auth_header = b'Basic ' + base64.b64encode(authpair) - conn = requests.Session() conn.auth = (user, passwd) - req = requests.Request(method='POST', url=service_url, auth=conn.auth, @@ -148,11 +141,9 @@ class LBRYAPIClient(object): r = req.prepare() http_response = conn.send(r) cookies = http_response.cookies - # print "Logged in" - uid = cookies.get(TWISTED_SESSION) api_key = APIKey.new(seed=uid) - # print "Created temporary api key" + else: # This is a client that already has a session, use it auth_header = auth diff --git a/lbrynet/lbrynet_daemon/auth/server.py b/lbrynet/lbrynet_daemon/auth/server.py index 10b6cf65b..1c675b5cf 100644 --- a/lbrynet/lbrynet_daemon/auth/server.py +++ b/lbrynet/lbrynet_daemon/auth/server.py @@ -1,16 +1,15 @@ import logging from decimal import Decimal -from twisted.web import server +from zope.interface import implements +from twisted.web import server, resource from twisted.internet import defer from txjsonrpc import jsonrpclib -from txjsonrpc.web import jsonrpc -from txjsonrpc.web.jsonrpc import Handler -from lbrynet.core.Error import InvalidAuthenticationToken, InvalidHeaderError -from lbrynet.lbrynet_daemon.auth.util import APIKey +from lbrynet.core.Error import InvalidAuthenticationToken, InvalidHeaderError, SubhandlerError +from lbrynet.conf import API_INTERFACE, REFERER, ORIGIN +from lbrynet.lbrynet_daemon.auth.util import APIKey, get_auth_message from lbrynet.lbrynet_daemon.auth.client import LBRY_SECRET -from lbrynet.conf import ALLOWED_DURING_STARTUP log = logging.getLogger(__name__) @@ -20,42 +19,166 @@ def default_decimal(obj): return float(obj) -def authorizer(cls): - cls.authorized_functions = [] - for methodname in dir(cls): - if methodname.startswith("jsonrpc_"): - method = getattr(cls, methodname) - if hasattr(method, '_auth_required'): - cls.authorized_functions.append(methodname.split("jsonrpc_")[1]) - return cls +class AuthorizedBase(object): + def __init__(self): + self.authorized_functions = [] + self.subhandlers = [] + self.callable_methods = {} + + for methodname in dir(self): + if methodname.startswith("jsonrpc_"): + method = getattr(self, methodname) + self.callable_methods.update({methodname.split("jsonrpc_")[1]: method}) + if hasattr(method, '_auth_required'): + self.authorized_functions.append(methodname.split("jsonrpc_")[1]) + elif not methodname.startswith("__"): + method = getattr(self, methodname) + if hasattr(method, '_subhandler'): + self.subhandlers.append(method) + + @staticmethod + def auth_required(f): + f._auth_required = True + return f + + @staticmethod + def subhandler(f): + f._subhandler = True + return f -def auth_required(f): - f._auth_required = True - return f +class AuthJSONRPCServer(AuthorizedBase): + """ + Authorized JSONRPC server used as the base class for the LBRY API + API methods are named with a leading "jsonrpc_" -@authorizer -class LBRYJSONRPCServer(jsonrpc.JSONRPC): + Decorators: + @AuthJSONRPCServer.auth_required: this requires the client include a valid hmac authentication token in their + request + + @AuthJSONRPCServer.subhandler: include the tagged method in the processing of requests, to allow inheriting + classes to modify request handling. Tagged methods will be passed the request + object, and return True when finished to indicate success + + Attributes: + allowed_during_startup (list): list of api methods that are callable before the server has finished + startup + + sessions (dict): dictionary of active session_id: lbrynet.lbrynet_daemon.auth.util.APIKey values + + authorized_functions (list): list of api methods that require authentication + + subhandlers (list): list of subhandlers + + callable_methods (dict): dictionary of api_callable_name: method values + """ + implements(resource.IResource) isLeaf = True + OK = 200 + UNAUTHORIZED = 401 + NOT_FOUND = 8001 + FAILURE = 8002 def __init__(self): - jsonrpc.JSONRPC.__init__(self) + AuthorizedBase.__init__(self) + self.allowed_during_startup = [] self.sessions = {} + def setup(self): + return NotImplementedError() + + def render(self, request): + assert self._check_headers(request), InvalidHeaderError + + session = request.getSession() + session_id = session.uid + + # if this is a new session, send a new secret and set the expiration, otherwise, session.touch() + if self._initialize_session(session_id): + def expire_session(): + self._unregister_user_session(session_id) + session.startCheckingExpiration() + session.notifyOnExpire(expire_session) + message = "OK" + request.setResponseCode(self.OK) + self._set_headers(request, message, True) + self._render_message(request, message) + return server.NOT_DONE_YET + session.touch() + + request.content.seek(0, 0) + content = request.content.read() + try: + parsed = jsonrpclib.loads(content) + except ValueError: + return server.failure + + function_name = parsed.get('method') + args = parsed.get('params') + id = parsed.get('id') + token = parsed.pop('hmac', None) + version = self._get_jsonrpc_version(parsed.get('jsonrpc'), id) + + try: + self._run_subhandlers(request) + except SubhandlerError: + return server.failure + + reply_with_next_secret = False + if function_name in self.authorized_functions: + try: + self._verify_token(session_id, parsed, token) + except InvalidAuthenticationToken: + log.warning("API validation failed") + request.setResponseCode(self.UNAUTHORIZED) + request.finish() + return server.NOT_DONE_YET + self._update_session_secret(session_id) + reply_with_next_secret = True + + try: + function = self._get_jsonrpc_method(function_name) + except Exception: + log.warning("Unknown method: %s", function_name) + return server.failure + + d = defer.maybeDeferred(function) if args == [{}] else defer.maybeDeferred(function, *args) + # cancel the response if the connection is broken + notify_finish = request.notifyFinish() + notify_finish.addErrback(self._response_failed, d) + d.addErrback(self._errback_render, id) + d.addCallback(self._callback_render, request, id, version, reply_with_next_secret) + d.addErrback(notify_finish.errback) + + return server.NOT_DONE_YET + def _register_user_session(self, session_id): + """ + Add or update a HMAC secret for a session + + @param session_id: + @return: secret + """ token = APIKey.new() self.sessions.update({session_id: token}) return token - def _responseFailed(self, err, call): + def _unregister_user_session(self, session_id): + log.info("Unregister API session") + del self.sessions[session_id] + + def _response_failed(self, err, call): log.debug(err.getTraceback()) - def _set_headers(self, request, data): - request.setHeader("Access-Control-Allow-Origin", "localhost") + def _set_headers(self, request, data, update_secret=False): + request.setHeader("Access-Control-Allow-Origin", API_INTERFACE) request.setHeader("Content-Type", "text/json") request.setHeader("Content-Length", str(len(data))) + if update_secret: + session_id = request.getSession().uid + request.setHeader(LBRY_SECRET, self.sessions.get(session_id).secret) def _render_message(self, request, message): request.write(message) @@ -64,112 +187,81 @@ class LBRYJSONRPCServer(jsonrpc.JSONRPC): def _check_headers(self, request): origin = request.getHeader("Origin") referer = request.getHeader("Referer") - - if origin not in [None, 'http://localhost:5279']: + if origin not in [None, ORIGIN]: log.warning("Attempted api call from %s", origin) - raise InvalidHeaderError - - if referer is not None and not referer.startswith('http://localhost:5279/'): + return False + if referer is not None and not referer.startswith(REFERER): log.warning("Attempted api call from %s", referer) - raise InvalidHeaderError + return False + return True - def _handle(self, request): - def _check_function_path(function_path): - if not self.announced_startup: - if function_path not in ALLOWED_DURING_STARTUP: - log.warning("Cannot call %s during startup", function_path) - raise Exception("Function not allowed") + def _check_function_path(self, function_path): + if function_path not in self.callable_methods: + log.warning("Unknown method: %s", function_path) + return False + if not self.announced_startup: + if function_path not in self.allowed_during_startup: + log.warning("Cannot call %s during startup", function_path) + return False + return True - def _get_function(function_path): - function = self._getFunction(function_path) - return function + def _get_jsonrpc_method(self, function_path): + assert self._check_function_path(function_path) + return self.callable_methods.get(function_path) - def _verify_token(session_id, message, token): - request.setHeader(LBRY_SECRET, "") - api_key = self.sessions.get(session_id, None) - assert api_key is not None, InvalidAuthenticationToken - r = api_key.compare_hmac(message, token) - assert r, InvalidAuthenticationToken - # log.info("Generating new token for next request") - self.sessions.update({session_id: APIKey.new(name=session_id)}) - request.setHeader(LBRY_SECRET, self.sessions.get(session_id).secret) - - session = request.getSession() - session_id = session.uid - session_store = self.sessions.get(session_id, False) - - if not session_store: - token = APIKey.new(seed=session_id, name=session_id) + def _initialize_session(self, session_id): + if not self.sessions.get(session_id, False): log.info("Initializing new api session") - self.sessions.update({session_id: token}) - # log.info("Generated token %s", str(self.sessions[session_id])) + self.sessions.update({session_id: APIKey.new(seed=session_id, name=session_id)}) + return True + return False - request.content.seek(0, 0) - content = request.content.read() + def _verify_token(self, session_id, message, token): + to_auth = get_auth_message(message) + api_key = self.sessions.get(session_id) + assert api_key.compare_hmac(to_auth, token), InvalidAuthenticationToken - parsed = jsonrpclib.loads(content) - - functionPath = parsed.get("method") - - _check_function_path(functionPath) - require_auth = functionPath in self.authorized_functions - if require_auth: - token = parsed.pop('hmac') - to_auth = functionPath.encode('hex') + str(parsed.get('id')).encode('hex') - _verify_token(session_id, to_auth.decode('hex'), token) - - args = parsed.get('params') - id = parsed.get('id') - version = parsed.get('jsonrpc') + def _update_session_secret(self, session_id): + # log.info("Generating new token for next request") + self.sessions.update({session_id: APIKey.new(name=session_id)}) + def _get_jsonrpc_version(self, version=None, id=None): if version: - version = int(float(version)) + version_for_return = int(float(version)) elif id and not version: - version = jsonrpclib.VERSION_1 + version_for_return = jsonrpclib.VERSION_1 else: - version = jsonrpclib.VERSION_PRE1 + version_for_return = jsonrpclib.VERSION_PRE1 + return version_for_return - if self.wallet_type == "lbryum" and functionPath in ['set_miner', 'get_miner_status']: - log.warning("Mining commands are not available in lbryum") - raise Exception("Command not available in lbryum") + def _run_subhandlers(self, request): + for handler in self.subhandlers: + try: + assert handler(request) + except Exception as err: + log.error(err.message) + raise SubhandlerError - try: - function = _get_function(functionPath) - if args == [{}]: - d = defer.maybeDeferred(function) - else: - d = defer.maybeDeferred(function, *args) - except jsonrpclib.Fault as f: - d = self._cbRender(f, request, id, version) - finally: - # cancel the response if the connection is broken - notify_finish = request.notifyFinish() - notify_finish.addErrback(self._responseFailed, d) - d.addErrback(self._ebRender, id) - d.addCallback(self._cbRender, request, id, version) - d.addErrback(notify_finish.errback) - def _cbRender(self, result, request, id, version): - if isinstance(result, Handler): - result = result.result - - if isinstance(result, dict): - result = result['result'] + def _callback_render(self, result, request, id, version, auth_required=False): + result_for_return = result if not isinstance(result, dict) else result['result'] if version == jsonrpclib.VERSION_PRE1: if not isinstance(result, jsonrpclib.Fault): - result = (result,) + result_for_return = (result_for_return,) # Convert the result (python) to JSON-RPC try: - s = jsonrpclib.dumps(result, version=version, default=default_decimal) - self._render_message(request, s) + encoded_message = jsonrpclib.dumps(result_for_return, version=version, default=default_decimal) + self._set_headers(request, encoded_message, auth_required) + self._render_message(request, encoded_message) except: - f = jsonrpclib.Fault(self.FAILURE, "can't serialize output") - s = jsonrpclib.dumps(f, version=version) - self._set_headers(request, s) - self._render_message(request, s) + fault = jsonrpclib.Fault(self.FAILURE, "can't serialize output") + encoded_message = jsonrpclib.dumps(fault, version=version) + self._set_headers(request, encoded_message) + self._render_message(request, encoded_message) - def _ebRender(self, failure, id): + def _errback_render(self, failure, id): + log.error("Request failed:") log.error(failure) log.error(failure.value) log.error(id) @@ -177,19 +269,6 @@ class LBRYJSONRPCServer(jsonrpc.JSONRPC): return failure.value return server.failure - def render(self, request): - try: - self._check_headers(request) - except InvalidHeaderError: - return server.failure - - try: - self._handle(request) - except: - return server.failure - - return server.NOT_DONE_YET - def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) diff --git a/lbrynet/lbrynet_daemon/auth/util.py b/lbrynet/lbrynet_daemon/auth/util.py index 1ed625569..8a1e078a4 100644 --- a/lbrynet/lbrynet_daemon/auth/util.py +++ b/lbrynet/lbrynet_daemon/auth/util.py @@ -84,4 +84,10 @@ def initialize_api_key_file(key_path): keys = {} new_api_key = APIKey.new(name=API_KEY_NAME) keys.update({new_api_key.name: new_api_key}) - save_api_keys(keys, key_path) \ No newline at end of file + save_api_keys(keys, key_path) + + +def get_auth_message(message_dict): + to_auth = message_dict.get('method').encode('hex') + to_auth += str(message_dict.get('id')).encode('hex') + return to_auth.decode('hex') \ No newline at end of file