diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 05f735352..26ef18e3b 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -2,7 +2,24 @@ Some network wide and also application specific parameters """ import os +import sys +from appdirs import user_data_dir +LINUX = 1 +DARWIN = 2 +WINDOWS = 3 + +if sys.platform.startswith("linux"): + platform = LINUX +elif sys.platform.startswith("darwin"): + platform = DARWIN +elif sys.platform.startswith("win"): + platform = WINDOWS + +if platform is LINUX: + DATA_DIR = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + DATA_DIR = user_data_dir("LBRY") IS_DEVELOPMENT_VERSION = False diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py index b63e1af25..07253cc2a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py @@ -35,10 +35,11 @@ def get_params_from_kwargs(params): def main(): - api = LBRYAPIClient() + api = LBRYAPIClient.config() try: - s = api.is_running() + status = api.daemon_status() + assert status.get('code', False) == "started" except: print "lbrynet-daemon isn't running" sys.exit(1) @@ -71,10 +72,10 @@ def main(): if meth in api.help(): try: if params: - r = LBRYAPIClient(service=meth)(params) + result = LBRYAPIClient.config(service=meth)(params) else: - r = LBRYAPIClient(service=meth)() - print json.dumps(r, sort_keys=True) + result = LBRYAPIClient.config(service=meth)() + print json.dumps(result, sort_keys=True) except: print "Something went wrong, here's the usage for %s:" % meth print api.help({'function': meth}) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index e6464a8e5..8b1290903 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -16,14 +16,10 @@ 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.lbrynet_daemon.auth.util import initialize_api_key_file from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_PORT, \ UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME - -# TODO: stop it! -if sys.platform != "darwin": - log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") -else: - log_dir = user_data_dir("LBRY") +from lbrynet.conf import DATA_DIR as log_dir if not os.path.isdir(log_dir): os.mkdir(log_dir) @@ -116,11 +112,13 @@ def start(): if args.launchui: d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) - checker = PasswordChecker() + pw_path = os.path.join(log_dir, ".api_keys") + initialize_api_key_file(pw_path) + checker = PasswordChecker.load_file(pw_path) realm = HttpPasswordRealm(lbry.root) - p = portal.Portal(realm, [checker, ]) + portal_to_realm = portal.Portal(realm, [checker, ]) factory = guard.BasicCredentialFactory('Login to lbrynet api') - protected_resource = guard.HTTPAuthSessionWrapper(p, [factory, ]) + protected_resource = guard.HTTPAuthSessionWrapper(portal_to_realm, [factory, ]) lbrynet_server = server.Site(protected_resource) lbrynet_server.requestFactory = LBRYDaemonRequest diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index e896c8b0f..60008d514 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -50,11 +50,15 @@ class MarketFeed(object): log.debug("Saving price update %f for %s" % (price, self.market)) self.rate = ExchangeRate(self.market, price, int(time.time())) + def _log_error(self): + log.warning("%s failed to update exchange rate information", self.name) + def _update_price(self): d = defer.succeed(self._make_request()) d.addCallback(self._handle_response) d.addCallback(self._subtract_fee) d.addCallback(self._save_price) + d.addErrback(lambda _: self._log_error()) def start(self): if not self._updater.running: diff --git a/lbrynet/lbrynet_daemon/auth/auth.py b/lbrynet/lbrynet_daemon/auth/auth.py index 57762e2b3..f68772fe2 100644 --- a/lbrynet/lbrynet_daemon/auth/auth.py +++ b/lbrynet/lbrynet_daemon/auth/auth.py @@ -1,26 +1,15 @@ import logging -import os -from zope.interface import implements, implementer +from zope.interface import 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: +class HttpPasswordRealm(object): def __init__(self, resource): self.resource = resource @@ -31,19 +20,28 @@ class HttpPasswordRealm: raise NotImplementedError() -class PasswordChecker: - implements(checkers.ICredentialsChecker) +@implementer(checkers.ICredentialsChecker) +class PasswordChecker(object): 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 __init__(self, passwords): + self.passwords = passwords + + @classmethod + def load_file(cls, key_path): + keys = load_api_keys(key_path) + return cls.load(keys) + + @classmethod + def load(cls, password_dict): + passwords = {key: password_dict[key].secret for key in password_dict} + return cls(passwords) 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: + if pw_match: return defer.succeed(creds.username) log.warning('Incorrect username or password') return defer.fail(cred_error.UnauthorizedLogin('Incorrect username or password')) diff --git a/lbrynet/lbrynet_daemon/auth/client.py b/lbrynet/lbrynet_daemon/auth/client.py index a4370e3b5..31f62949c 100644 --- a/lbrynet/lbrynet_daemon/auth/client.py +++ b/lbrynet/lbrynet_daemon/auth/client.py @@ -1,12 +1,4 @@ -try: - import http.client as httplib -except ImportError: - import httplib -try: - import urllib.parse as urlparse -except ImportError: - import urlparse - +import urlparse import logging import requests import os @@ -15,10 +7,12 @@ 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 +from lbrynet.conf import DATA_DIR log = logging.getLogger(__name__) USER_AGENT = "AuthServiceProxy/0.1" +TWISTED_SESSION = "TWISTED_SESSION" +LBRY_SECRET = "LBRY_SECRET" HTTP_TIMEOUT = 30 @@ -29,77 +23,15 @@ class JSONRPCException(Exception): 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 + def __init__(self, key, timeout, connection, count, service, cookies, auth, url, login_url): 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.__api_key = key + 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}) - + self.__url = url + self.__auth_header = auth + self.__conn = connection + self.__cookies = cookies def __getattr__(self, name): if name.startswith('__') and name.endswith('__'): @@ -107,11 +39,11 @@ class LBRYAPIClient(object): 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, + return LBRYAPIClient(key=self.__api_key, + timeout=HTTP_TIMEOUT, connection=self.__conn, - service=name, count=self.__id_count, + service=name, cookies=self.__cookies, auth=self.__auth_header, url=self.__url, @@ -124,7 +56,7 @@ class LBRYAPIClient(object): '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')) + token = self.__api_key.get_hmac(to_auth.decode('hex')) pre_auth_postdata.update({'hmac': token}) postdata = json.dumps(pre_auth_postdata) service_url = self.__service_url @@ -142,12 +74,14 @@ class LBRYAPIClient(object): cookies=cookies) r = req.prepare() http_response = self.__conn.send(r) - self.__cookies = http_response.cookies + cookies = http_response.cookies headers = http_response.headers - next_secret = headers.get('Next-Secret', False) + next_secret = headers.get(LBRY_SECRET, False) if next_secret: - cookies.update({'secret': next_secret}) + # print "Next secret: %s" % next_secret + self.__api_key.secret = next_secret + self.__cookies = cookies # print "Postdata: %s" % postdata if http_response is None: @@ -165,4 +99,65 @@ class LBRYAPIClient(object): raise JSONRPCException({ 'code': -343, 'message': 'missing JSON-RPC result'}) else: - return response['result'] \ No newline at end of file + return response['result'] + + @classmethod + def config(cls, key_name=None, key=None, pw_path=None, timeout=HTTP_TIMEOUT, connection=None, count=0, + service=None, cookies=None, auth=None, url=None, login_url=None): + api_key_name = API_KEY_NAME if not key_name else key_name + pw_path = os.path.join(DATA_DIR, ".api_keys") if not pw_path else pw_path + + if not key: + keys = load_api_keys(pw_path) + api_key = keys.get(api_key_name, False) + else: + api_key = APIKey(name=api_key_name, secret=key) + + if login_url is None: + service_url = "http://%s:%s@%s:%i/%s" % (api_key_name, api_key.secret, API_INTERFACE, API_PORT, API_ADDRESS) + else: + service_url = login_url + + id_count = count + + if auth is None and connection is None and cookies is None and url is None: + # This is a new client instance, initialize the auth header and start a session + url = urlparse.urlparse(service_url) + (user, passwd) = (url.username, url.password) + try: + user = user.encode('utf8') + except AttributeError: + pass + try: + passwd = passwd.encode('utf8') + except AttributeError: + pass + authpair = user + b':' + passwd + auth_header = b'Basic ' + base64.b64encode(authpair) + + conn = requests.Session() + conn.auth = (user, passwd) + + req = requests.Request(method='POST', + url=service_url, + auth=conn.auth, + headers={'Host': url.hostname, + 'User-Agent': USER_AGENT, + 'Authorization': auth_header, + 'Content-type': 'application/json'},) + r = req.prepare() + http_response = conn.send(r) + cookies = http_response.cookies + # print "Logged in" + + uid = cookies.get(TWISTED_SESSION) + api_key = APIKey.new(seed=uid) + # print "Created temporary api key" + else: + # This is a client that already has a session, use it + auth_header = auth + conn = connection + assert cookies.get(LBRY_SECRET, False), "Missing cookie" + secret = cookies.get(LBRY_SECRET) + api_key = APIKey(secret, api_key_name) + return cls(api_key, timeout, conn, id_count, service, cookies, auth_header, url, service_url) \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/auth/server.py b/lbrynet/lbrynet_daemon/auth/server.py index b2ad51402..10b6cf65b 100644 --- a/lbrynet/lbrynet_daemon/auth/server.py +++ b/lbrynet/lbrynet_daemon/auth/server.py @@ -9,6 +9,7 @@ from txjsonrpc.web.jsonrpc import Handler from lbrynet.core.Error import InvalidAuthenticationToken, InvalidHeaderError from lbrynet.lbrynet_daemon.auth.util import APIKey +from lbrynet.lbrynet_daemon.auth.client import LBRY_SECRET from lbrynet.conf import ALLOWED_DURING_STARTUP log = logging.getLogger(__name__) @@ -84,21 +85,21 @@ class LBRYJSONRPCServer(jsonrpc.JSONRPC): return function def _verify_token(session_id, message, token): - request.setHeader("Next-Secret", "") + request.setHeader(LBRY_SECRET, "") api_key = self.sessions.get(session_id, None) assert api_key is not None, InvalidAuthenticationToken r = api_key.compare_hmac(message, token) assert r, InvalidAuthenticationToken # log.info("Generating new token for next request") - self.sessions.update({session_id: APIKey.new()}) - request.setHeader("Next-Secret", self.sessions.get(session_id).token()) + self.sessions.update({session_id: APIKey.new(name=session_id)}) + request.setHeader(LBRY_SECRET, self.sessions.get(session_id).secret) session = request.getSession() session_id = session.uid session_store = self.sessions.get(session_id, False) if not session_store: - token = APIKey.new(seed=session_id) + token = APIKey.new(seed=session_id, name=session_id) log.info("Initializing new api session") self.sessions.update({session_id: token}) # log.info("Generated token %s", str(self.sessions[session_id])) @@ -191,3 +192,5 @@ class LBRYJSONRPCServer(jsonrpc.JSONRPC): def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) + + diff --git a/lbrynet/lbrynet_daemon/auth/util.py b/lbrynet/lbrynet_daemon/auth/util.py index 69daeee56..1ed625569 100644 --- a/lbrynet/lbrynet_daemon/auth/util.py +++ b/lbrynet/lbrynet_daemon/auth/util.py @@ -22,24 +22,20 @@ def generate_key(x=None): 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}}) +class APIKey(object): + def __init__(self, secret, name, expiration=None): + self.secret = secret + self.name = name + self.expiration = 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 new(cls, seed=None, name=None, expiration=None): + secret = generate_key(seed) + key_name = name if name else sha(secret) + return APIKey(secret, key_name, expiration) def _raw_key(self): - return base58.b58decode(self.token()) + return base58.b58decode(self.secret) def get_hmac(self, message): decoded_key = self._raw_key() @@ -56,27 +52,36 @@ class APIKey(dict): 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() + with open(path, "r") as f: + data = yaml.load(f.read()) - keys = {key: APIKey(data[key], name=key)[key] for key in data} + keys_for_return = {} + for key_name in data: + key = data[key_name] + secret = key['secret'] + expiration = key['expiration'] + keys_for_return.update({key_name: APIKey(secret, key_name, expiration)}) - return keys + return keys_for_return def save_api_keys(keys, path): - data = yaml.safe_dump(dict(keys)) - f = open(path, "w") - f.write(data) - f.close() + with open(path, "w") as f: + key_dict = {keys[key_name].name: {'secret': keys[key_name].secret, + 'expiration': keys[key_name].expiration} + for key_name in keys} + data = yaml.safe_dump(key_dict) + f.write(data) + + +def initialize_api_key_file(key_path): + if not os.path.isfile(key_path): + keys = {} + new_api_key = APIKey.new(name=API_KEY_NAME) + keys.update({new_api_key.name: new_api_key}) + save_api_keys(keys, key_path) \ No newline at end of file