api sessions
-user starts a httpauthsession with an api key and name -user initializes jsonrpc hmac secret to sha256 of session id -server sends new random hmac secret after each api call -a user without an authenticated session will get a authorization error
This commit is contained in:
parent
4d52a33ee5
commit
130f9cfc4d
10 changed files with 577 additions and 117 deletions
|
@ -71,6 +71,11 @@ CURRENCIES = {
|
||||||
'USD': {'type': 'fiat'},
|
'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'
|
LOGGLY_TOKEN = 'LJEzATH4AzRgAwxjAP00LwZ2YGx3MwVgZTMuBQZ3MQuxLmOv'
|
||||||
|
|
||||||
ANALYTICS_ENDPOINT = 'https://api.segment.io/v1'
|
ANALYTICS_ENDPOINT = 'https://api.segment.io/v1'
|
||||||
|
|
|
@ -87,4 +87,10 @@ class NoSuchStreamHashError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class InvalidBlobHashError(Exception):
|
class InvalidBlobHashError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InvalidHeaderError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InvalidAuthenticationToken(Exception):
|
||||||
pass
|
pass
|
|
@ -40,6 +40,7 @@ from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream
|
||||||
from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher
|
from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher
|
||||||
from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager
|
from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager
|
||||||
from lbrynet.lbrynet_daemon.Lighthouse import LighthouseClient
|
from lbrynet.lbrynet_daemon.Lighthouse import LighthouseClient
|
||||||
|
from lbrynet.lbrynet_daemon.auth.server import LBRYJSONRPCServer, auth_required, authorizer
|
||||||
from lbrynet.metadata.LBRYMetadata import Metadata, verify_name_characters
|
from lbrynet.metadata.LBRYMetadata import Metadata, verify_name_characters
|
||||||
from lbrynet.core import log_support
|
from lbrynet.core import log_support
|
||||||
from lbrynet.core import utils
|
from lbrynet.core import utils
|
||||||
|
@ -48,7 +49,7 @@ from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings
|
||||||
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, \
|
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, \
|
||||||
KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, DEFAULT_WALLET, \
|
KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, DEFAULT_WALLET, \
|
||||||
DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, \
|
DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, \
|
||||||
LOG_POST_URL, LOG_FILE_NAME, REFLECTOR_SERVERS, SEARCH_SERVERS
|
LOG_POST_URL, LOG_FILE_NAME, REFLECTOR_SERVERS, SEARCH_SERVERS, ALLOWED_DURING_STARTUP
|
||||||
from lbrynet.conf import DEFAULT_SD_DOWNLOAD_TIMEOUT
|
from lbrynet.conf import DEFAULT_SD_DOWNLOAD_TIMEOUT
|
||||||
from lbrynet.conf import DEFAULT_TIMEOUT
|
from lbrynet.conf import DEFAULT_TIMEOUT
|
||||||
from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob, BlobStreamDescriptorReader
|
from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob, BlobStreamDescriptorReader
|
||||||
|
@ -117,11 +118,6 @@ CONNECTION_PROBLEM_CODES = [
|
||||||
(CONNECT_CODE_WALLET, "Synchronization with the blockchain is lagging... if this continues try restarting LBRY")
|
(CONNECT_CODE_WALLET, "Synchronization with the blockchain is lagging... if this continues try restarting LBRY")
|
||||||
]
|
]
|
||||||
|
|
||||||
ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run',
|
|
||||||
'get_time_behind_blockchain', 'stop',
|
|
||||||
'daemon_status', 'get_start_notice',
|
|
||||||
'version', 'get_search_servers']
|
|
||||||
|
|
||||||
BAD_REQUEST = 400
|
BAD_REQUEST = 400
|
||||||
NOT_FOUND = 404
|
NOT_FOUND = 404
|
||||||
OK_CODE = 200
|
OK_CODE = 200
|
||||||
|
@ -138,15 +134,14 @@ class Parameters(object):
|
||||||
self.__dict__.update(kwargs)
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
|
||||||
class LBRYDaemon(jsonrpc.JSONRPC):
|
@authorizer
|
||||||
|
class LBRYDaemon(LBRYJSONRPCServer):
|
||||||
"""
|
"""
|
||||||
LBRYnet daemon, a jsonrpc interface to lbry functions
|
LBRYnet daemon, a jsonrpc interface to lbry functions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
isLeaf = True
|
|
||||||
|
|
||||||
def __init__(self, root, wallet_type=None):
|
def __init__(self, root, wallet_type=None):
|
||||||
jsonrpc.JSONRPC.__init__(self)
|
LBRYJSONRPCServer.__init__(self)
|
||||||
reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
|
reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
|
||||||
|
|
||||||
self.startup_status = STARTUP_STAGES[0]
|
self.startup_status = STARTUP_STAGES[0]
|
||||||
|
@ -397,99 +392,6 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
f.write("rpcpassword=" + password)
|
f.write("rpcpassword=" + password)
|
||||||
log.info("Done writing lbrycrd.conf")
|
log.info("Done writing lbrycrd.conf")
|
||||||
|
|
||||||
def _responseFailed(self, err, call):
|
|
||||||
log.debug(err.getTraceback())
|
|
||||||
|
|
||||||
def render(self, request):
|
|
||||||
origin = request.getHeader("Origin")
|
|
||||||
referer = request.getHeader("Referer")
|
|
||||||
|
|
||||||
if origin not in [None, 'http://localhost:5279']:
|
|
||||||
log.warning("Attempted api call from %s", origin)
|
|
||||||
return server.failure
|
|
||||||
|
|
||||||
if referer is not None and not referer.startswith('http://localhost:5279/'):
|
|
||||||
log.warning("Attempted api call from %s", referer)
|
|
||||||
return server.failure
|
|
||||||
|
|
||||||
request.content.seek(0, 0)
|
|
||||||
# Unmarshal the JSON-RPC data.
|
|
||||||
content = request.content.read()
|
|
||||||
parsed = jsonrpclib.loads(content)
|
|
||||||
functionPath = parsed.get("method")
|
|
||||||
args = parsed.get('params')
|
|
||||||
|
|
||||||
#TODO convert args to correct types if possible
|
|
||||||
|
|
||||||
id = parsed.get('id')
|
|
||||||
version = parsed.get('jsonrpc')
|
|
||||||
if version:
|
|
||||||
version = int(float(version))
|
|
||||||
elif id and not version:
|
|
||||||
version = jsonrpclib.VERSION_1
|
|
||||||
else:
|
|
||||||
version = jsonrpclib.VERSION_PRE1
|
|
||||||
# XXX this all needs to be re-worked to support logic for multiple
|
|
||||||
# versions...
|
|
||||||
|
|
||||||
if not self.announced_startup:
|
|
||||||
if functionPath not in ALLOWED_DURING_STARTUP:
|
|
||||||
return server.failure
|
|
||||||
|
|
||||||
if self.wallet_type == "lbryum" and functionPath in ['set_miner', 'get_miner_status']:
|
|
||||||
return server.failure
|
|
||||||
|
|
||||||
try:
|
|
||||||
function = self._getFunction(functionPath)
|
|
||||||
except jsonrpclib.Fault, f:
|
|
||||||
self._cbRender(f, request, id, version)
|
|
||||||
else:
|
|
||||||
request.setHeader("Access-Control-Allow-Origin", "localhost")
|
|
||||||
request.setHeader("content-type", "text/json")
|
|
||||||
if args == [{}]:
|
|
||||||
d = defer.maybeDeferred(function)
|
|
||||||
else:
|
|
||||||
d = defer.maybeDeferred(function, *args)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
return server.NOT_DONE_YET
|
|
||||||
|
|
||||||
def _cbRender(self, result, request, id, version):
|
|
||||||
def default_decimal(obj):
|
|
||||||
if isinstance(obj, Decimal):
|
|
||||||
return float(obj)
|
|
||||||
|
|
||||||
if isinstance(result, Handler):
|
|
||||||
result = result.result
|
|
||||||
|
|
||||||
if isinstance(result, dict):
|
|
||||||
result = result['result']
|
|
||||||
|
|
||||||
if version == jsonrpclib.VERSION_PRE1:
|
|
||||||
if not isinstance(result, jsonrpclib.Fault):
|
|
||||||
result = (result,)
|
|
||||||
# Convert the result (python) to JSON-RPC
|
|
||||||
try:
|
|
||||||
s = jsonrpclib.dumps(result, version=version, default=default_decimal)
|
|
||||||
except:
|
|
||||||
f = jsonrpclib.Fault(self.FAILURE, "can't serialize output")
|
|
||||||
s = jsonrpclib.dumps(f, version=version)
|
|
||||||
|
|
||||||
request.setHeader("content-length", str(len(s)))
|
|
||||||
request.write(s)
|
|
||||||
request.finish()
|
|
||||||
|
|
||||||
def _ebRender(self, failure, id):
|
|
||||||
if isinstance(failure.value, jsonrpclib.Fault):
|
|
||||||
return failure.value
|
|
||||||
log.error(failure)
|
|
||||||
return jsonrpclib.Fault(self.FAILURE, "error")
|
|
||||||
|
|
||||||
def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, host_ui=True):
|
def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, host_ui=True):
|
||||||
def _log_starting_vals():
|
def _log_starting_vals():
|
||||||
log.info("Starting balance: " + str(self.session.wallet.wallet_balance))
|
log.info("Starting balance: " + str(self.session.wallet.wallet_balance))
|
||||||
|
@ -1435,9 +1337,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
def _search(self, search):
|
def _search(self, search):
|
||||||
return self.lighthouse_client.search(search)
|
return self.lighthouse_client.search(search)
|
||||||
|
|
||||||
def _render_response(self, result, code):
|
@auth_required
|
||||||
return defer.succeed({'result': result, 'code': code})
|
|
||||||
|
|
||||||
def jsonrpc_is_running(self):
|
def jsonrpc_is_running(self):
|
||||||
"""
|
"""
|
||||||
Check if lbrynet daemon is running
|
Check if lbrynet daemon is running
|
||||||
|
@ -1454,6 +1354,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
else:
|
else:
|
||||||
return self._render_response(False, OK_CODE)
|
return self._render_response(False, OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_daemon_status(self):
|
def jsonrpc_daemon_status(self):
|
||||||
"""
|
"""
|
||||||
Get lbrynet daemon status information
|
Get lbrynet daemon status information
|
||||||
|
@ -1488,6 +1389,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
log.info("daemon status: " + str(r))
|
log.info("daemon status: " + str(r))
|
||||||
return self._render_response(r, OK_CODE)
|
return self._render_response(r, OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_is_first_run(self):
|
def jsonrpc_is_first_run(self):
|
||||||
"""
|
"""
|
||||||
Check if this is the first time lbrynet daemon has been run
|
Check if this is the first time lbrynet daemon has been run
|
||||||
|
@ -1508,6 +1410,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_start_notice(self):
|
def jsonrpc_get_start_notice(self):
|
||||||
"""
|
"""
|
||||||
Get special message to be displayed at startup
|
Get special message to be displayed at startup
|
||||||
|
@ -1527,6 +1430,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
else:
|
else:
|
||||||
self._render_response(self.startup_message, OK_CODE)
|
self._render_response(self.startup_message, OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_version(self):
|
def jsonrpc_version(self):
|
||||||
"""
|
"""
|
||||||
Get lbry version information
|
Get lbry version information
|
||||||
|
@ -1561,6 +1465,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
log.info("Get version info: " + json.dumps(msg))
|
log.info("Get version info: " + json.dumps(msg))
|
||||||
return self._render_response(msg, OK_CODE)
|
return self._render_response(msg, OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_settings(self):
|
def jsonrpc_get_settings(self):
|
||||||
"""
|
"""
|
||||||
Get lbrynet daemon settings
|
Get lbrynet daemon settings
|
||||||
|
@ -1589,6 +1494,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
log.info("Get daemon settings")
|
log.info("Get daemon settings")
|
||||||
return self._render_response(self.session_settings, OK_CODE)
|
return self._render_response(self.session_settings, OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_set_settings(self, p):
|
def jsonrpc_set_settings(self, p):
|
||||||
"""
|
"""
|
||||||
Set lbrynet daemon settings
|
Set lbrynet daemon settings
|
||||||
|
@ -1616,6 +1522,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_help(self, p=None):
|
def jsonrpc_help(self, p=None):
|
||||||
"""
|
"""
|
||||||
Function to retrieve docstring for API function
|
Function to retrieve docstring for API function
|
||||||
|
@ -1640,6 +1547,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
else:
|
else:
|
||||||
return self._render_response(self.jsonrpc_help.__doc__, OK_CODE)
|
return self._render_response(self.jsonrpc_help.__doc__, OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_balance(self):
|
def jsonrpc_get_balance(self):
|
||||||
"""
|
"""
|
||||||
Get balance
|
Get balance
|
||||||
|
@ -1653,6 +1561,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
log.info("Get balance")
|
log.info("Get balance")
|
||||||
return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE)
|
return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_stop(self):
|
def jsonrpc_stop(self):
|
||||||
"""
|
"""
|
||||||
Stop lbrynet-daemon
|
Stop lbrynet-daemon
|
||||||
|
@ -1672,6 +1581,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return self._render_response("Shutting down", OK_CODE)
|
return self._render_response("Shutting down", OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_lbry_files(self):
|
def jsonrpc_get_lbry_files(self):
|
||||||
"""
|
"""
|
||||||
Get LBRY files
|
Get LBRY files
|
||||||
|
@ -1698,6 +1608,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_lbry_file(self, p):
|
def jsonrpc_get_lbry_file(self, p):
|
||||||
"""
|
"""
|
||||||
Get lbry file
|
Get lbry file
|
||||||
|
@ -1727,6 +1638,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_resolve_name(self, p):
|
def jsonrpc_resolve_name(self, p):
|
||||||
"""
|
"""
|
||||||
Resolve stream info from a LBRY uri
|
Resolve stream info from a LBRY uri
|
||||||
|
@ -1748,6 +1660,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure)
|
d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_claim_info(self, p):
|
def jsonrpc_get_claim_info(self, p):
|
||||||
"""
|
"""
|
||||||
Resolve claim info from a LBRY uri
|
Resolve claim info from a LBRY uri
|
||||||
|
@ -1772,6 +1685,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def _process_get_parameters(self, p):
|
def _process_get_parameters(self, p):
|
||||||
"""Extract info from input parameters and fill in default values for `get` call."""
|
"""Extract info from input parameters and fill in default values for `get` call."""
|
||||||
# TODO: this process can be abstracted s.t. each method
|
# TODO: this process can be abstracted s.t. each method
|
||||||
|
@ -1793,6 +1707,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
name=name
|
name=name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get(self, p):
|
def jsonrpc_get(self, p):
|
||||||
"""Download stream from a LBRY uri.
|
"""Download stream from a LBRY uri.
|
||||||
|
|
||||||
|
@ -1823,6 +1738,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda message: self._render_response(message, OK_CODE))
|
d.addCallback(lambda message: self._render_response(message, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_stop_lbry_file(self, p):
|
def jsonrpc_stop_lbry_file(self, p):
|
||||||
"""
|
"""
|
||||||
Stop lbry file
|
Stop lbry file
|
||||||
|
@ -1848,6 +1764,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_start_lbry_file(self, p):
|
def jsonrpc_start_lbry_file(self, p):
|
||||||
"""
|
"""
|
||||||
Stop lbry file
|
Stop lbry file
|
||||||
|
@ -1872,6 +1789,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_est_cost(self, p):
|
def jsonrpc_get_est_cost(self, p):
|
||||||
"""
|
"""
|
||||||
Get estimated cost for a lbry uri
|
Get estimated cost for a lbry uri
|
||||||
|
@ -1893,6 +1811,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_search_nametrie(self, p):
|
def jsonrpc_search_nametrie(self, p):
|
||||||
"""
|
"""
|
||||||
Search the nametrie for claims
|
Search the nametrie for claims
|
||||||
|
@ -1929,6 +1848,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_delete_lbry_file(self, p):
|
def jsonrpc_delete_lbry_file(self, p):
|
||||||
"""
|
"""
|
||||||
Delete a lbry file
|
Delete a lbry file
|
||||||
|
@ -1958,6 +1878,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_publish(self, p):
|
def jsonrpc_publish(self, p):
|
||||||
"""
|
"""
|
||||||
Make a new name claim and publish associated data to lbrynet
|
Make a new name claim and publish associated data to lbrynet
|
||||||
|
@ -2034,6 +1955,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_abandon_claim(self, p):
|
def jsonrpc_abandon_claim(self, p):
|
||||||
"""
|
"""
|
||||||
Abandon a name and reclaim credits from the claim
|
Abandon a name and reclaim credits from the claim
|
||||||
|
@ -2060,6 +1982,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_abandon_name(self, p):
|
def jsonrpc_abandon_name(self, p):
|
||||||
"""
|
"""
|
||||||
DEPRECIATED, use abandon_claim
|
DEPRECIATED, use abandon_claim
|
||||||
|
@ -2072,7 +1995,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return self.jsonrpc_abandon_claim(p)
|
return self.jsonrpc_abandon_claim(p)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_support_claim(self, p):
|
def jsonrpc_support_claim(self, p):
|
||||||
"""
|
"""
|
||||||
Support a name claim
|
Support a name claim
|
||||||
|
@ -2092,6 +2015,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_name_claims(self):
|
def jsonrpc_get_name_claims(self):
|
||||||
"""
|
"""
|
||||||
Get my name claims
|
Get my name claims
|
||||||
|
@ -2115,6 +2039,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_claims_for_name(self, p):
|
def jsonrpc_get_claims_for_name(self, p):
|
||||||
"""
|
"""
|
||||||
Get claims for a name
|
Get claims for a name
|
||||||
|
@ -2130,6 +2055,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_transaction_history(self):
|
def jsonrpc_get_transaction_history(self):
|
||||||
"""
|
"""
|
||||||
Get transaction history
|
Get transaction history
|
||||||
|
@ -2144,6 +2070,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_transaction(self, p):
|
def jsonrpc_get_transaction(self, p):
|
||||||
"""
|
"""
|
||||||
Get a decoded transaction from a txid
|
Get a decoded transaction from a txid
|
||||||
|
@ -2160,6 +2087,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_address_is_mine(self, p):
|
def jsonrpc_address_is_mine(self, p):
|
||||||
"""
|
"""
|
||||||
Checks if an address is associated with the current wallet.
|
Checks if an address is associated with the current wallet.
|
||||||
|
@ -2177,7 +2105,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_public_key_from_wallet(self, p):
|
def jsonrpc_get_public_key_from_wallet(self, p):
|
||||||
"""
|
"""
|
||||||
Get public key from wallet address
|
Get public key from wallet address
|
||||||
|
@ -2192,6 +2120,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d = self.session.wallet.get_pub_keys(wallet)
|
d = self.session.wallet.get_pub_keys(wallet)
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_time_behind_blockchain(self):
|
def jsonrpc_get_time_behind_blockchain(self):
|
||||||
"""
|
"""
|
||||||
Get number of blocks behind the blockchain
|
Get number of blocks behind the blockchain
|
||||||
|
@ -2215,6 +2144,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_new_address(self):
|
def jsonrpc_get_new_address(self):
|
||||||
"""
|
"""
|
||||||
Generate a new wallet address
|
Generate a new wallet address
|
||||||
|
@ -2234,6 +2164,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda address: self._render_response(address, OK_CODE))
|
d.addCallback(lambda address: self._render_response(address, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_send_amount_to_address(self, p):
|
def jsonrpc_send_amount_to_address(self, p):
|
||||||
"""
|
"""
|
||||||
Send credits to an address
|
Send credits to an address
|
||||||
|
@ -2258,6 +2189,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda _: self._render_response(True, OK_CODE))
|
d.addCallback(lambda _: self._render_response(True, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_best_blockhash(self):
|
def jsonrpc_get_best_blockhash(self):
|
||||||
"""
|
"""
|
||||||
Get hash of most recent block
|
Get hash of most recent block
|
||||||
|
@ -2272,6 +2204,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_block(self, p):
|
def jsonrpc_get_block(self, p):
|
||||||
"""
|
"""
|
||||||
Get contents of a block
|
Get contents of a block
|
||||||
|
@ -2294,6 +2227,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_claims_for_tx(self, p):
|
def jsonrpc_get_claims_for_tx(self, p):
|
||||||
"""
|
"""
|
||||||
Get claims for tx
|
Get claims for tx
|
||||||
|
@ -2313,6 +2247,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_download_descriptor(self, p):
|
def jsonrpc_download_descriptor(self, p):
|
||||||
"""
|
"""
|
||||||
Download and return a sd blob
|
Download and return a sd blob
|
||||||
|
@ -2329,6 +2264,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: self._render_response(False, OK_CODE))
|
d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: self._render_response(False, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_nametrie(self):
|
def jsonrpc_get_nametrie(self):
|
||||||
"""
|
"""
|
||||||
Get the nametrie
|
Get the nametrie
|
||||||
|
@ -2344,6 +2280,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_set_miner(self, p):
|
def jsonrpc_set_miner(self, p):
|
||||||
"""
|
"""
|
||||||
Start of stop the miner, function only available when lbrycrd is set as the wallet
|
Start of stop the miner, function only available when lbrycrd is set as the wallet
|
||||||
|
@ -2363,6 +2300,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_miner_status(self):
|
def jsonrpc_get_miner_status(self):
|
||||||
"""
|
"""
|
||||||
Get status of miner
|
Get status of miner
|
||||||
|
@ -2377,6 +2315,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_log(self, p):
|
def jsonrpc_log(self, p):
|
||||||
"""
|
"""
|
||||||
Log message
|
Log message
|
||||||
|
@ -2391,6 +2330,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
log.info("API client log request: %s" % message)
|
log.info("API client log request: %s" % message)
|
||||||
return self._render_response(True, OK_CODE)
|
return self._render_response(True, OK_CODE)
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_upload_log(self, p=None):
|
def jsonrpc_upload_log(self, p=None):
|
||||||
"""
|
"""
|
||||||
Upload log
|
Upload log
|
||||||
|
@ -2432,6 +2372,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda _: self._render_response(True, OK_CODE))
|
d.addCallback(lambda _: self._render_response(True, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_configure_ui(self, p):
|
def jsonrpc_configure_ui(self, p):
|
||||||
"""
|
"""
|
||||||
Configure the UI being hosted
|
Configure the UI being hosted
|
||||||
|
@ -2456,6 +2397,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_reveal(self, p):
|
def jsonrpc_reveal(self, p):
|
||||||
"""
|
"""
|
||||||
Reveal a file or directory in file browser
|
Reveal a file or directory in file browser
|
||||||
|
@ -2475,6 +2417,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda _: self._render_response(True, OK_CODE))
|
d.addCallback(lambda _: self._render_response(True, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_peers_for_hash(self, p):
|
def jsonrpc_get_peers_for_hash(self, p):
|
||||||
"""
|
"""
|
||||||
Get peers for blob hash
|
Get peers for blob hash
|
||||||
|
@ -2492,6 +2435,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_announce_all_blobs_to_dht(self):
|
def jsonrpc_announce_all_blobs_to_dht(self):
|
||||||
"""
|
"""
|
||||||
Announce all blobs to the dht
|
Announce all blobs to the dht
|
||||||
|
@ -2506,6 +2450,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda _: self._render_response("Announced", OK_CODE))
|
d.addCallback(lambda _: self._render_response("Announced", OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_reflect(self, p):
|
def jsonrpc_reflect(self, p):
|
||||||
"""
|
"""
|
||||||
Reflect a stream
|
Reflect a stream
|
||||||
|
@ -2522,6 +2467,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallbacks(lambda _: self._render_response(True, OK_CODE), lambda err: self._render_response(err.getTraceback(), OK_CODE))
|
d.addCallbacks(lambda _: self._render_response(True, OK_CODE), lambda err: self._render_response(err.getTraceback(), OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_blob_hashes(self):
|
def jsonrpc_get_blob_hashes(self):
|
||||||
"""
|
"""
|
||||||
Returns all blob hashes
|
Returns all blob hashes
|
||||||
|
@ -2536,6 +2482,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_reflect_all_blobs(self):
|
def jsonrpc_reflect_all_blobs(self):
|
||||||
"""
|
"""
|
||||||
Reflects all saved blobs
|
Reflects all saved blobs
|
||||||
|
@ -2551,6 +2498,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
||||||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@auth_required
|
||||||
def jsonrpc_get_search_servers(self):
|
def jsonrpc_get_search_servers(self):
|
||||||
"""
|
"""
|
||||||
Get list of lighthouse servers
|
Get list of lighthouse servers
|
||||||
|
|
|
@ -2,8 +2,7 @@ import sys
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from lbrynet.conf import API_CONNECTION_STRING
|
from lbrynet.lbrynet_daemon.auth.client import LBRYAPIClient
|
||||||
from jsonrpc.proxy import JSONRPCProxy
|
|
||||||
|
|
||||||
help_msg = "Usage: lbrynet-cli method json-args\n" \
|
help_msg = "Usage: lbrynet-cli method json-args\n" \
|
||||||
+ "Examples: " \
|
+ "Examples: " \
|
||||||
|
@ -36,7 +35,7 @@ def get_params_from_kwargs(params):
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
api = JSONRPCProxy.from_url(API_CONNECTION_STRING)
|
api = LBRYAPIClient()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
s = api.is_running()
|
s = api.is_running()
|
||||||
|
@ -72,9 +71,9 @@ def main():
|
||||||
if meth in api.help():
|
if meth in api.help():
|
||||||
try:
|
try:
|
||||||
if params:
|
if params:
|
||||||
r = api.call(meth, params)
|
r = LBRYAPIClient(service=meth)(params)
|
||||||
else:
|
else:
|
||||||
r = api.call(meth)
|
r = LBRYAPIClient(service=meth)()
|
||||||
print json.dumps(r, sort_keys=True)
|
print json.dumps(r, sort_keys=True)
|
||||||
except:
|
except:
|
||||||
print "Something went wrong, here's the usage for %s:" % meth
|
print "Something went wrong, here's the usage for %s:" % meth
|
||||||
|
|
|
@ -7,12 +7,15 @@ import sys
|
||||||
import socket
|
import socket
|
||||||
from appdirs import user_data_dir
|
from appdirs import user_data_dir
|
||||||
|
|
||||||
from twisted.web import server
|
from twisted.web import server, guard
|
||||||
from twisted.internet import reactor, defer
|
from twisted.internet import defer, reactor
|
||||||
|
from twisted.cred import portal
|
||||||
|
|
||||||
from jsonrpc.proxy import JSONRPCProxy
|
from jsonrpc.proxy import JSONRPCProxy
|
||||||
|
|
||||||
from lbrynet.core import log_support
|
from lbrynet.core import log_support
|
||||||
from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer, LBRYDaemonRequest
|
from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer, LBRYDaemonRequest
|
||||||
|
from lbrynet.lbrynet_daemon.auth.auth import PasswordChecker, HttpPasswordRealm
|
||||||
from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_PORT, \
|
from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_PORT, \
|
||||||
UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME
|
UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME
|
||||||
|
|
||||||
|
@ -113,8 +116,14 @@ def start():
|
||||||
if args.launchui:
|
if args.launchui:
|
||||||
d.addCallback(lambda _: webbrowser.open(UI_ADDRESS))
|
d.addCallback(lambda _: webbrowser.open(UI_ADDRESS))
|
||||||
|
|
||||||
lbrynet_server = server.Site(lbry.root)
|
checker = PasswordChecker()
|
||||||
|
realm = HttpPasswordRealm(lbry.root)
|
||||||
|
p = portal.Portal(realm, [checker, ])
|
||||||
|
factory = guard.BasicCredentialFactory('Login to lbrynet api')
|
||||||
|
protected_resource = guard.HTTPAuthSessionWrapper(p, [factory, ])
|
||||||
|
lbrynet_server = server.Site(protected_resource)
|
||||||
lbrynet_server.requestFactory = LBRYDaemonRequest
|
lbrynet_server.requestFactory = LBRYDaemonRequest
|
||||||
|
|
||||||
reactor.listenTCP(API_PORT, lbrynet_server, interface=API_INTERFACE)
|
reactor.listenTCP(API_PORT, lbrynet_server, interface=API_INTERFACE)
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
||||||
|
@ -127,4 +136,4 @@ def start():
|
||||||
return
|
return
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
start()
|
start()
|
0
lbrynet/lbrynet_daemon/auth/__init__.py
Normal file
0
lbrynet/lbrynet_daemon/auth/__init__.py
Normal file
50
lbrynet/lbrynet_daemon/auth/auth.py
Normal file
50
lbrynet/lbrynet_daemon/auth/auth.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from zope.interface import implements, implementer
|
||||||
|
from twisted.cred import portal, checkers, credentials, error as cred_error
|
||||||
|
from twisted.internet import defer
|
||||||
|
from twisted.web import resource
|
||||||
|
from lbrynet.lbrynet_daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME, save_api_keys
|
||||||
|
from lbrynet.lbrynet_daemon.LBRYDaemon import log_dir as DATA_DIR
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# initialize api key if none exist
|
||||||
|
if not os.path.isfile(os.path.join(DATA_DIR, ".api_keys")):
|
||||||
|
keys = {}
|
||||||
|
api_key = APIKey.new()
|
||||||
|
api_key.rename(API_KEY_NAME)
|
||||||
|
keys.update(api_key)
|
||||||
|
save_api_keys(keys, os.path.join(DATA_DIR, ".api_keys"))
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(portal.IRealm)
|
||||||
|
class HttpPasswordRealm:
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
def requestAvatar(self, avatarId, mind, *interfaces):
|
||||||
|
log.info("Processing request for %s", avatarId)
|
||||||
|
if resource.IResource in interfaces:
|
||||||
|
return (resource.IResource, self.resource, lambda: None)
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordChecker:
|
||||||
|
implements(checkers.ICredentialsChecker)
|
||||||
|
credentialInterfaces = (credentials.IUsernamePassword,)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
keys = load_api_keys(os.path.join(DATA_DIR, ".api_keys"))
|
||||||
|
self.passwords = {key: keys[key]['token'] for key in keys}
|
||||||
|
|
||||||
|
def requestAvatarId(self, creds):
|
||||||
|
if creds.username in self.passwords:
|
||||||
|
pw = self.passwords.get(creds.username)
|
||||||
|
pw_match = creds.checkPassword(pw)
|
||||||
|
if pw_match is True:
|
||||||
|
return defer.succeed(creds.username)
|
||||||
|
log.warning('Incorrect username or password')
|
||||||
|
return defer.fail(cred_error.UnauthorizedLogin('Incorrect username or password'))
|
||||||
|
|
168
lbrynet/lbrynet_daemon/auth/client.py
Normal file
168
lbrynet/lbrynet_daemon/auth/client.py
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
try:
|
||||||
|
import http.client as httplib
|
||||||
|
except ImportError:
|
||||||
|
import httplib
|
||||||
|
try:
|
||||||
|
import urllib.parse as urlparse
|
||||||
|
except ImportError:
|
||||||
|
import urlparse
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
|
||||||
|
from lbrynet.lbrynet_daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME
|
||||||
|
from lbrynet.conf import API_INTERFACE, API_ADDRESS, API_PORT
|
||||||
|
from lbrynet.lbrynet_daemon.LBRYDaemon import log_dir as DATA_DIR
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
USER_AGENT = "AuthServiceProxy/0.1"
|
||||||
|
HTTP_TIMEOUT = 30
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCException(Exception):
|
||||||
|
def __init__(self, rpc_error):
|
||||||
|
Exception.__init__(self)
|
||||||
|
self.error = rpc_error
|
||||||
|
|
||||||
|
|
||||||
|
class LBRYAPIClient(object):
|
||||||
|
__api_token = None
|
||||||
|
|
||||||
|
def __init__(self, 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):
|
||||||
|
self.__api_key_name = API_KEY_NAME if not key_name else key_name
|
||||||
|
self.__api_token = key
|
||||||
|
self.__pw_path = os.path.join(DATA_DIR, ".api_keys") if not pw_path else pw_path
|
||||||
|
self.__service_name = service
|
||||||
|
|
||||||
|
if not key:
|
||||||
|
keys = load_api_keys(self.__pw_path)
|
||||||
|
api_key = keys.get(self.__api_key_name, False)
|
||||||
|
self.__api_token = api_key['token']
|
||||||
|
self.__api_key_obj = api_key
|
||||||
|
else:
|
||||||
|
self.__api_key_obj = APIKey({'token': key})
|
||||||
|
|
||||||
|
if login_url is None:
|
||||||
|
self.__service_url = "http://%s:%s@%s:%i/%s" % (self.__api_key_name, self.__api_token,
|
||||||
|
API_INTERFACE, API_PORT, API_ADDRESS)
|
||||||
|
else:
|
||||||
|
self.__service_url = login_url
|
||||||
|
|
||||||
|
self.__id_count = count
|
||||||
|
|
||||||
|
if auth is None and connection is None and cookies is None and url is None:
|
||||||
|
self.__url = urlparse.urlparse(self.__service_url)
|
||||||
|
(user, passwd) = (self.__url.username, self.__url.password)
|
||||||
|
try:
|
||||||
|
user = user.encode('utf8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
passwd = passwd.encode('utf8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
authpair = user + b':' + passwd
|
||||||
|
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
|
||||||
|
|
||||||
|
self.__conn = requests.Session()
|
||||||
|
self.__conn.auth = (user, passwd)
|
||||||
|
|
||||||
|
req = requests.Request(method='POST',
|
||||||
|
url=self.__service_url,
|
||||||
|
auth=self.__conn.auth,
|
||||||
|
headers={'Host': self.__url.hostname,
|
||||||
|
'User-Agent': USER_AGENT,
|
||||||
|
'Authorization': self.__auth_header,
|
||||||
|
'Content-type': 'application/json'},)
|
||||||
|
r = req.prepare()
|
||||||
|
http_response = self.__conn.send(r)
|
||||||
|
cookies = http_response.cookies
|
||||||
|
self.__cookies = cookies
|
||||||
|
# print "Logged in"
|
||||||
|
|
||||||
|
uid = cookies.get('TWISTED_SESSION')
|
||||||
|
api_key = APIKey.new(seed=uid)
|
||||||
|
# print "Created temporary api key"
|
||||||
|
|
||||||
|
self.__api_token = api_key.token()
|
||||||
|
self.__api_key_obj = api_key
|
||||||
|
else:
|
||||||
|
self.__auth_header = auth
|
||||||
|
self.__conn = connection
|
||||||
|
self.__cookies = cookies
|
||||||
|
self.__url = url
|
||||||
|
|
||||||
|
if cookies.get("secret", False):
|
||||||
|
self.__api_token = cookies.get("secret")
|
||||||
|
self.__api_key_obj = APIKey({'name': self.__api_key_name, 'token': self.__api_token})
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name.startswith('__') and name.endswith('__'):
|
||||||
|
# Python internal stuff
|
||||||
|
raise AttributeError
|
||||||
|
if self.__service_name is not None:
|
||||||
|
name = "%s.%s" % (self.__service_name, name)
|
||||||
|
return LBRYAPIClient(key_name=self.__api_key_name,
|
||||||
|
key=self.__api_token,
|
||||||
|
connection=self.__conn,
|
||||||
|
service=name,
|
||||||
|
count=self.__id_count,
|
||||||
|
cookies=self.__cookies,
|
||||||
|
auth=self.__auth_header,
|
||||||
|
url=self.__url,
|
||||||
|
login_url=self.__service_url)
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
self.__id_count += 1
|
||||||
|
pre_auth_postdata = {'version': '1.1',
|
||||||
|
'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_obj.get_hmac(to_auth.decode('hex'))
|
||||||
|
pre_auth_postdata.update({'hmac': token})
|
||||||
|
postdata = json.dumps(pre_auth_postdata)
|
||||||
|
service_url = self.__service_url
|
||||||
|
auth_header = self.__auth_header
|
||||||
|
cookies = self.__cookies
|
||||||
|
host = self.__url.hostname
|
||||||
|
|
||||||
|
req = requests.Request(method='POST',
|
||||||
|
url=service_url,
|
||||||
|
data=postdata,
|
||||||
|
headers={'Host': host,
|
||||||
|
'User-Agent': USER_AGENT,
|
||||||
|
'Authorization': auth_header,
|
||||||
|
'Content-type': 'application/json'},
|
||||||
|
cookies=cookies)
|
||||||
|
r = req.prepare()
|
||||||
|
http_response = self.__conn.send(r)
|
||||||
|
self.__cookies = http_response.cookies
|
||||||
|
headers = http_response.headers
|
||||||
|
next_secret = headers.get('Next-Secret', False)
|
||||||
|
|
||||||
|
if next_secret:
|
||||||
|
cookies.update({'secret': next_secret})
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
if response['error'] is not None:
|
||||||
|
raise JSONRPCException(response['error'])
|
||||||
|
elif 'result' not in response:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -343, 'message': 'missing JSON-RPC result'})
|
||||||
|
else:
|
||||||
|
return response['result']
|
193
lbrynet/lbrynet_daemon/auth/server.py
Normal file
193
lbrynet/lbrynet_daemon/auth/server.py
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from twisted.web import server
|
||||||
|
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.conf import ALLOWED_DURING_STARTUP
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def default_decimal(obj):
|
||||||
|
if isinstance(obj, Decimal):
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def auth_required(f):
|
||||||
|
f._auth_required = True
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
@authorizer
|
||||||
|
class LBRYJSONRPCServer(jsonrpc.JSONRPC):
|
||||||
|
|
||||||
|
isLeaf = True
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
jsonrpc.JSONRPC.__init__(self)
|
||||||
|
self.sessions = {}
|
||||||
|
|
||||||
|
def _register_user_session(self, session_id):
|
||||||
|
token = APIKey.new()
|
||||||
|
self.sessions.update({session_id: token})
|
||||||
|
return token
|
||||||
|
|
||||||
|
def _responseFailed(self, err, call):
|
||||||
|
log.debug(err.getTraceback())
|
||||||
|
|
||||||
|
def _set_headers(self, request, data):
|
||||||
|
request.setHeader("Access-Control-Allow-Origin", "localhost")
|
||||||
|
request.setHeader("Content-Type", "text/json")
|
||||||
|
request.setHeader("Content-Length", str(len(data)))
|
||||||
|
|
||||||
|
def _render_message(self, request, message):
|
||||||
|
request.write(message)
|
||||||
|
request.finish()
|
||||||
|
|
||||||
|
def _check_headers(self, request):
|
||||||
|
origin = request.getHeader("Origin")
|
||||||
|
referer = request.getHeader("Referer")
|
||||||
|
|
||||||
|
if origin not in [None, 'http://localhost:5279']:
|
||||||
|
log.warning("Attempted api call from %s", origin)
|
||||||
|
raise InvalidHeaderError
|
||||||
|
|
||||||
|
if referer is not None and not referer.startswith('http://localhost:5279/'):
|
||||||
|
log.warning("Attempted api call from %s", referer)
|
||||||
|
raise InvalidHeaderError
|
||||||
|
|
||||||
|
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 _get_function(function_path):
|
||||||
|
function = self._getFunction(function_path)
|
||||||
|
return function
|
||||||
|
|
||||||
|
def _verify_token(session_id, message, token):
|
||||||
|
request.setHeader("Next-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()})
|
||||||
|
request.setHeader("Next-Secret", self.sessions.get(session_id).token())
|
||||||
|
|
||||||
|
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)
|
||||||
|
log.info("Initializing new api session")
|
||||||
|
self.sessions.update({session_id: token})
|
||||||
|
# log.info("Generated token %s", str(self.sessions[session_id]))
|
||||||
|
|
||||||
|
request.content.seek(0, 0)
|
||||||
|
content = request.content.read()
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
if version:
|
||||||
|
version = int(float(version))
|
||||||
|
elif id and not version:
|
||||||
|
version = jsonrpclib.VERSION_1
|
||||||
|
else:
|
||||||
|
version = jsonrpclib.VERSION_PRE1
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
if version == jsonrpclib.VERSION_PRE1:
|
||||||
|
if not isinstance(result, jsonrpclib.Fault):
|
||||||
|
result = (result,)
|
||||||
|
# Convert the result (python) to JSON-RPC
|
||||||
|
try:
|
||||||
|
s = jsonrpclib.dumps(result, version=version, default=default_decimal)
|
||||||
|
self._render_message(request, s)
|
||||||
|
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)
|
||||||
|
|
||||||
|
def _ebRender(self, failure, id):
|
||||||
|
log.error(failure)
|
||||||
|
log.error(failure.value)
|
||||||
|
log.error(id)
|
||||||
|
if isinstance(failure.value, jsonrpclib.Fault):
|
||||||
|
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})
|
82
lbrynet/lbrynet_daemon/auth/util.py
Normal file
82
lbrynet/lbrynet_daemon/auth/util.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import base58
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import yaml
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
API_KEY_NAME = "api"
|
||||||
|
|
||||||
|
|
||||||
|
def sha(x):
|
||||||
|
h = hashlib.sha256(x).digest()
|
||||||
|
return base58.b58encode(h)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_key(x=None):
|
||||||
|
if x is None:
|
||||||
|
return sha(os.urandom(256))
|
||||||
|
else:
|
||||||
|
return sha(x)
|
||||||
|
|
||||||
|
|
||||||
|
class APIKey(dict):
|
||||||
|
def __init__(self, key, name=None):
|
||||||
|
self.key = key if isinstance(key, str) else key['token']
|
||||||
|
self.name = name if name else hashlib.sha256(self.key).hexdigest()
|
||||||
|
self.expiration = None if isinstance(key, str) else key.get('expiration', None)
|
||||||
|
self.update({self.name: {'token': self.key, 'expiration': self.expiration}})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new(cls, expiration=None, seed=None, name=None):
|
||||||
|
key_val = generate_key(seed)
|
||||||
|
key = {'token': key_val, 'expiration': expiration}
|
||||||
|
return APIKey(key, name)
|
||||||
|
|
||||||
|
def token(self):
|
||||||
|
return self[self.name]['token']
|
||||||
|
|
||||||
|
def _raw_key(self):
|
||||||
|
return base58.b58decode(self.token())
|
||||||
|
|
||||||
|
def get_hmac(self, message):
|
||||||
|
decoded_key = self._raw_key()
|
||||||
|
signature = hmac.new(decoded_key, message, hashlib.sha256)
|
||||||
|
return base58.b58encode(signature.digest())
|
||||||
|
|
||||||
|
def compare_hmac(self, message, token):
|
||||||
|
decoded_token = base58.b58decode(token)
|
||||||
|
target = base58.b58decode(self.get_hmac(message))
|
||||||
|
try:
|
||||||
|
assert len(decoded_token) == len(target), "Length mismatch"
|
||||||
|
r = hmac.compare_digest(decoded_token, target)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
return r
|
||||||
|
|
||||||
|
def rename(self, name):
|
||||||
|
old = self.keys()[0]
|
||||||
|
t = self.pop(old)
|
||||||
|
self.update({name: t})
|
||||||
|
|
||||||
|
|
||||||
|
def load_api_keys(path):
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
raise Exception("Invalid api key path")
|
||||||
|
|
||||||
|
f = open(path, "r")
|
||||||
|
data = yaml.load(f.read())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
keys = {key: APIKey(data[key], name=key)[key] for key in data}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
|
||||||
|
|
||||||
|
def save_api_keys(keys, path):
|
||||||
|
data = yaml.safe_dump(dict(keys))
|
||||||
|
f = open(path, "w")
|
||||||
|
f.write(data)
|
||||||
|
f.close()
|
Loading…
Add table
Reference in a new issue