forked from LBRYCommunity/lbry-sdk
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'},
|
||||
}
|
||||
|
||||
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'
|
||||
|
|
|
@ -87,4 +87,10 @@ class NoSuchStreamHashError(Exception):
|
|||
|
||||
|
||||
class InvalidBlobHashError(Exception):
|
||||
pass
|
||||
|
||||
class InvalidHeaderError(Exception):
|
||||
pass
|
||||
|
||||
class InvalidAuthenticationToken(Exception):
|
||||
pass
|
|
@ -40,6 +40,7 @@ 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.metadata.LBRYMetadata import Metadata, verify_name_characters
|
||||
from lbrynet.core import log_support
|
||||
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, \
|
||||
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
|
||||
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_TIMEOUT
|
||||
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")
|
||||
]
|
||||
|
||||
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
|
||||
NOT_FOUND = 404
|
||||
OK_CODE = 200
|
||||
|
@ -138,15 +134,14 @@ class Parameters(object):
|
|||
self.__dict__.update(kwargs)
|
||||
|
||||
|
||||
class LBRYDaemon(jsonrpc.JSONRPC):
|
||||
@authorizer
|
||||
class LBRYDaemon(LBRYJSONRPCServer):
|
||||
"""
|
||||
LBRYnet daemon, a jsonrpc interface to lbry functions
|
||||
"""
|
||||
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, root, wallet_type=None):
|
||||
jsonrpc.JSONRPC.__init__(self)
|
||||
LBRYJSONRPCServer.__init__(self)
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
|
||||
|
||||
self.startup_status = STARTUP_STAGES[0]
|
||||
|
@ -397,99 +392,6 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
f.write("rpcpassword=" + password)
|
||||
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 _log_starting_vals():
|
||||
log.info("Starting balance: " + str(self.session.wallet.wallet_balance))
|
||||
|
@ -1435,9 +1337,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
def _search(self, search):
|
||||
return self.lighthouse_client.search(search)
|
||||
|
||||
def _render_response(self, result, code):
|
||||
return defer.succeed({'result': result, 'code': code})
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_is_running(self):
|
||||
"""
|
||||
Check if lbrynet daemon is running
|
||||
|
@ -1454,6 +1354,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
else:
|
||||
return self._render_response(False, OK_CODE)
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_daemon_status(self):
|
||||
"""
|
||||
Get lbrynet daemon status information
|
||||
|
@ -1488,6 +1389,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -1508,6 +1410,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_start_notice(self):
|
||||
"""
|
||||
Get special message to be displayed at startup
|
||||
|
@ -1527,6 +1430,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
else:
|
||||
self._render_response(self.startup_message, OK_CODE)
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_version(self):
|
||||
"""
|
||||
Get lbry version information
|
||||
|
@ -1561,6 +1465,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
log.info("Get version info: " + json.dumps(msg))
|
||||
return self._render_response(msg, OK_CODE)
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_settings(self):
|
||||
"""
|
||||
Get lbrynet daemon settings
|
||||
|
@ -1589,6 +1494,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
log.info("Get daemon settings")
|
||||
return self._render_response(self.session_settings, OK_CODE)
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_set_settings(self, p):
|
||||
"""
|
||||
Set lbrynet daemon settings
|
||||
|
@ -1616,6 +1522,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_help(self, p=None):
|
||||
"""
|
||||
Function to retrieve docstring for API function
|
||||
|
@ -1640,6 +1547,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
else:
|
||||
return self._render_response(self.jsonrpc_help.__doc__, OK_CODE)
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_balance(self):
|
||||
"""
|
||||
Get balance
|
||||
|
@ -1653,6 +1561,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -1672,6 +1581,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return self._render_response("Shutting down", OK_CODE)
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_lbry_files(self):
|
||||
"""
|
||||
Get LBRY files
|
||||
|
@ -1698,6 +1608,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_lbry_file(self, p):
|
||||
"""
|
||||
Get lbry file
|
||||
|
@ -1727,6 +1638,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -1748,6 +1660,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -1772,6 +1685,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@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
|
||||
|
@ -1793,6 +1707,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
name=name
|
||||
)
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get(self, p):
|
||||
"""Download stream from a LBRY uri.
|
||||
|
||||
|
@ -1823,6 +1738,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda message: self._render_response(message, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_stop_lbry_file(self, p):
|
||||
"""
|
||||
Stop lbry file
|
||||
|
@ -1848,6 +1764,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_start_lbry_file(self, p):
|
||||
"""
|
||||
Stop lbry file
|
||||
|
@ -1872,6 +1789,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -1893,6 +1811,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_search_nametrie(self, p):
|
||||
"""
|
||||
Search the nametrie for claims
|
||||
|
@ -1929,6 +1848,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_delete_lbry_file(self, p):
|
||||
"""
|
||||
Delete a lbry file
|
||||
|
@ -1958,6 +1878,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_publish(self, p):
|
||||
"""
|
||||
Make a new name claim and publish associated data to lbrynet
|
||||
|
@ -2034,6 +1955,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_abandon_claim(self, p):
|
||||
"""
|
||||
Abandon a name and reclaim credits from the claim
|
||||
|
@ -2060,6 +1982,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_abandon_name(self, p):
|
||||
"""
|
||||
DEPRECIATED, use abandon_claim
|
||||
|
@ -2072,7 +1995,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return self.jsonrpc_abandon_claim(p)
|
||||
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_support_claim(self, p):
|
||||
"""
|
||||
Support a name claim
|
||||
|
@ -2092,6 +2015,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_name_claims(self):
|
||||
"""
|
||||
Get my name claims
|
||||
|
@ -2115,6 +2039,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_claims_for_name(self, p):
|
||||
"""
|
||||
Get claims for a name
|
||||
|
@ -2130,6 +2055,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_transaction_history(self):
|
||||
"""
|
||||
Get transaction history
|
||||
|
@ -2144,6 +2070,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -2160,6 +2087,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_address_is_mine(self, p):
|
||||
"""
|
||||
Checks if an address is associated with the current wallet.
|
||||
|
@ -2177,7 +2105,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_public_key_from_wallet(self, p):
|
||||
"""
|
||||
Get public key from wallet address
|
||||
|
@ -2192,6 +2120,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -2215,6 +2144,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_new_address(self):
|
||||
"""
|
||||
Generate a new wallet address
|
||||
|
@ -2234,6 +2164,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda address: self._render_response(address, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_send_amount_to_address(self, p):
|
||||
"""
|
||||
Send credits to an address
|
||||
|
@ -2258,6 +2189,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -2272,6 +2204,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -2294,6 +2227,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -2313,6 +2247,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_download_descriptor(self, p):
|
||||
"""
|
||||
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))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_nametrie(self):
|
||||
"""
|
||||
Get the nametrie
|
||||
|
@ -2344,6 +2280,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_set_miner(self, p):
|
||||
"""
|
||||
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))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_miner_status(self):
|
||||
"""
|
||||
Get status of miner
|
||||
|
@ -2377,6 +2315,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_log(self, p):
|
||||
"""
|
||||
Log message
|
||||
|
@ -2391,6 +2330,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
@ -2432,6 +2372,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda _: self._render_response(True, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_configure_ui(self, p):
|
||||
"""
|
||||
Configure the UI being hosted
|
||||
|
@ -2456,6 +2397,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_reveal(self, p):
|
||||
"""
|
||||
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))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_peers_for_hash(self, p):
|
||||
"""
|
||||
Get peers for blob hash
|
||||
|
@ -2492,6 +2435,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_announce_all_blobs_to_dht(self):
|
||||
"""
|
||||
Announce all blobs to the dht
|
||||
|
@ -2506,6 +2450,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda _: self._render_response("Announced", OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_reflect(self, p):
|
||||
"""
|
||||
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))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_get_blob_hashes(self):
|
||||
"""
|
||||
Returns all blob hashes
|
||||
|
@ -2536,6 +2482,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
d.addCallback(lambda r: self._render_response(r, OK_CODE))
|
||||
return d
|
||||
|
||||
@auth_required
|
||||
def jsonrpc_reflect_all_blobs(self):
|
||||
"""
|
||||
Reflects all saved blobs
|
||||
|
@ -2551,6 +2498,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
|
|||
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
|
||||
|
|
|
@ -2,8 +2,7 @@ import sys
|
|||
import json
|
||||
import argparse
|
||||
|
||||
from lbrynet.conf import API_CONNECTION_STRING
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
from lbrynet.lbrynet_daemon.auth.client import LBRYAPIClient
|
||||
|
||||
help_msg = "Usage: lbrynet-cli method json-args\n" \
|
||||
+ "Examples: " \
|
||||
|
@ -36,7 +35,7 @@ def get_params_from_kwargs(params):
|
|||
|
||||
|
||||
def main():
|
||||
api = JSONRPCProxy.from_url(API_CONNECTION_STRING)
|
||||
api = LBRYAPIClient()
|
||||
|
||||
try:
|
||||
s = api.is_running()
|
||||
|
@ -72,9 +71,9 @@ def main():
|
|||
if meth in api.help():
|
||||
try:
|
||||
if params:
|
||||
r = api.call(meth, params)
|
||||
r = LBRYAPIClient(service=meth)(params)
|
||||
else:
|
||||
r = api.call(meth)
|
||||
r = LBRYAPIClient(service=meth)()
|
||||
print json.dumps(r, sort_keys=True)
|
||||
except:
|
||||
print "Something went wrong, here's the usage for %s:" % meth
|
||||
|
|
|
@ -7,12 +7,15 @@ import sys
|
|||
import socket
|
||||
from appdirs import user_data_dir
|
||||
|
||||
from twisted.web import server
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.web import server, guard
|
||||
from twisted.internet import defer, reactor
|
||||
from twisted.cred import portal
|
||||
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
|
||||
from lbrynet.core import log_support
|
||||
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, \
|
||||
UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME
|
||||
|
||||
|
@ -113,8 +116,14 @@ def start():
|
|||
if args.launchui:
|
||||
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
|
||||
|
||||
reactor.listenTCP(API_PORT, lbrynet_server, interface=API_INTERFACE)
|
||||
reactor.run()
|
||||
|
||||
|
@ -127,4 +136,4 @@ def start():
|
|||
return
|
||||
|
||||
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…
Reference in a new issue