diff --git a/clicada/cli/arg.py b/clicada/cli/arg.py index 508056e..1b8cabd 100644 --- a/clicada/cli/arg.py +++ b/clicada/cli/arg.py @@ -13,6 +13,7 @@ from chainlib.chain import ChainSpec import clicada.cli.user as cmd_user import clicada.cli.tag as cmd_tag from clicada.cli.auth import PGPAuthCrypt +from clicada.cli.http import HTTPSession script_dir = os.path.dirname(os.path.realpath(__file__)) data_dir = os.path.join(script_dir, '..', 'data') @@ -68,9 +69,12 @@ class CmdCtrl: else: self.config = chainlib.eth.cli.Config.from_args(self.cmd_args, base_config_dir=base_config_dir, extra_args=extra_args) - self.__auth = self.auth(self.get('AUTH_TYPE')) + self.auth(self.get('AUTH_TYPE')) self.config.add(False, '_SEQ') + + self.config.censor('AUTH_PASSPHRASE') + logger.debug('loaded config:\n{}'.format(self.config)) self.chain_spec = ChainSpec.from_chain_str(self.config.get('CHAIN_SPEC')) @@ -78,6 +82,9 @@ class CmdCtrl: self.rpc = chainlib.eth.cli.Rpc() self.__conn = self.rpc.connect_by_config(self.config) + self.remote_openers = { + 'meta': HTTPSession(self.get('META_URL'), auth=self.__auth), + } def auth(self, typ): @@ -110,3 +117,7 @@ class CmdCtrl: def execute(self): self.cmd_mod.execute(self) + + + def opener(self, k): + return self.remote_openers[k] diff --git a/clicada/cli/auth.py b/clicada/cli/auth.py index c8a699b..be02d95 100644 --- a/clicada/cli/auth.py +++ b/clicada/cli/auth.py @@ -5,6 +5,7 @@ import logging # external imports import gnupg +from usumbufu.client.hoba import HobaClientSession # local imports from clicada.error import AuthError @@ -12,7 +13,10 @@ from clicada.error import AuthError logg = logging.getLogger(__name__) -class PGPAuthCrypt: +class PGPAuthCrypt(HobaClientSession): + + typ = 'gnupg' + alg = '969' def __init__(self, db_dir, auth_key, pgp_dir=None): self.db_dir = db_dir @@ -51,3 +55,21 @@ class PGPAuthCrypt: if not self.secret.ok: raise AuthError('could not decrypt encryption secret. wrong password?') f.close() + + + def sign_auth_challenge(self, plaintext, hoba, encoding): + r = self.gpg.sign(plaintext, passphrase=self.passphrase, detach=True) + + if encoding == 'base64': + r = r.data + + hoba.signature = r + return str(hoba) + + + def __str__(self): + return 'clicada hoba/pgp auth' + + + def __repr__(self): + return 'clicada hoba/pgp auth' diff --git a/clicada/cli/http.py b/clicada/cli/http.py new file mode 100644 index 0000000..f5eebde --- /dev/null +++ b/clicada/cli/http.py @@ -0,0 +1,52 @@ +# standard imports +import hashlib +import urllib.parse +import os +import logging + +# external imports +from usumbufu.client.base import ( + ClientSession, + BaseTokenStore, + ) +from usumbufu.client.bearer import BearerClientSession + +logg = logging.getLogger(__name__) + + +class HTTPSession: + + token_dir = '/run/user/{}/clicada/usumbufu/.token'.format(os.getuid()) + + def __init__(self, url, auth=None): + logg.debug('auth auth {}'.format(auth)) + self.base_url = url + url_parts = urllib.parse.urlparse(self.base_url) + self.origin = urllib.parse.urljoin(url_parts[0], url_parts[1]) + + h = hashlib.sha256() + h.update(self.base_url.encode('utf-8')) + z = h.digest() + + token_store_dir = os.path.join(self.token_dir, z.hex()) + self.token_store = BaseTokenStore(path=token_store_dir) + + self.session = ClientSession(self.origin, token_store=self.token_store) + bearer_handler = BearerClientSession(self.origin, token_store=self.token_store) + self.session.add_subhandler(bearer_handler) + + if auth != None: + self.session.add_subhandler(auth) + + self.opener = urllib.request.build_opener(self.session) + + + def open(self, endpoint): + url = urllib.parse.urljoin(self.base_url, endpoint) + logg.debug('open {} with opener {}'.format(url, self)) + r = self.opener.open(url) + return r.read().decode('utf-8') + + + def __str__(self): + return str(self.session) diff --git a/clicada/cli/user.py b/clicada/cli/user.py index ca36c45..2080e22 100644 --- a/clicada/cli/user.py +++ b/clicada/cli/user.py @@ -26,7 +26,7 @@ logg = logging.getLogger(__name__) def process_args(argparser): argparser.add_argument('-m', '--method', type=str, help='lookup method') argparser.add_argument('--meta-url', dest='meta_url', type=str, help='Url to retrieve metadata from') - argparser.add_argument('-f', '--force-update', dest='force_update', type=str, help='Update records of mutable entries') + argparser.add_argument('-f', '--force-update', dest='force_update', action='store_true', help='Update records of mutable entries') argparser.add_argument('identifier', type=str, help='user identifier') @@ -55,7 +55,7 @@ def execute(ctrl): store_path = '.clicada' user_phone_file_label = 'phone' - user_phone_store = FileUserStore(ctrl.chain(), user_phone_file_label, store_path, int(ctrl.get('FILESTORE_TTL'))) + user_phone_store = FileUserStore(ctrl.opener('meta'), ctrl.chain(), user_phone_file_label, store_path, int(ctrl.get('FILESTORE_TTL'))) user_address = user_phone_store.by_phone(ctrl.get('_IDENTIFIER'), update=ctrl.get('_FORCE')) if user_address == None: @@ -74,9 +74,9 @@ def execute(ctrl): token_store = FileTokenStore(ctrl.chain(), ctrl.conn(), 'token', store_path) user_address_file_label = 'address' - user_address_store = FileUserStore(ctrl.chain(), user_address_file_label, store_path, int(ctrl.get('FILESTORE_TTL'))) + user_address_store = FileUserStore(ctrl.opener('meta'), ctrl.chain(), user_address_file_label, store_path, int(ctrl.get('FILESTORE_TTL'))) - r = user_address_store.by_address(user_address) + r = user_address_store.by_address(user_address, update=ctrl.get('_FORCE')) print("""Phone: {} Network address: {} diff --git a/clicada/user/file.py b/clicada/user/file.py index 33184cb..66b90ba 100644 --- a/clicada/user/file.py +++ b/clicada/user/file.py @@ -33,12 +33,13 @@ class Account(Person): def apply_custom(self, custom_data): self.tags = custom_data['tags'] + logg.debug('tags are now {}'.format(self.tags)) class FileUserStore: - def __init__(self, chain_spec, label, store_base_path, ttl): + def __init__(self, metadata_opener, chain_spec, label, store_base_path, ttl): invalidate_before = datetime.datetime.now() - datetime.timedelta(seconds=ttl) self.invalidate_before = int(invalidate_before.timestamp()) self.have_xattr = False @@ -53,6 +54,7 @@ class FileUserStore: self.memstore_entity = MemDictStore() os.makedirs(self.store_path, exist_ok=True) self.__validate_dir() + self.metadata_opener = metadata_opener def __validate_dir(self): @@ -177,7 +179,7 @@ class FileUserStore: logg.debug('no address found for phone {}: {}'.format(phone, e)) return None - self.put(phone_file, user_address) + self.put(phone_file, user_address, force=update) return user_address @@ -189,12 +191,12 @@ class FileUserStore: try: v = self.get(address, ignore_expired=ignore_expired) v = json.loads(v) - person = Person() + person = Account() try: person_data = person.deserialize(person_data=v) except Exception as e: person_data = v - return str(person_data) + return person_data except FileNotFoundError: pass except ExpiredRecordError as e: @@ -202,25 +204,28 @@ class FileUserStore: pass address = strip_0x(address) - getter = MetadataRequestsHandler(MetadataPointer.PERSON, bytes.fromhex(address)) + getter = self.metadata_opener + + ptr = generate_metadata_pointer(bytes.fromhex(address), MetadataPointer.PERSON) r = None try: - r = getter.query() - except requests.exceptions.HTTPError as e: + r = getter.open(ptr) + except Exception as e: logg.debug('no metadata found for {}: {}'.format(address, e)) return address - data = r.json() + data = json.loads(r) person = Account() person_data = person.deserialize(person_data=data) - self.put(address, json.dumps(person_data.serialize())) + self.put(address, json.dumps(person_data.serialize()), force=update) - getter = MetadataRequestsHandler(MetadataPointer.CUSTOM, bytes.fromhex(address)) + ptr = generate_metadata_pointer(bytes.fromhex(address), MetadataPointer.CUSTOM) r = None try: - r = getter.query() - person_data.apply_custom(r.json()) - except requests.exceptions.HTTPError as e: + r = getter.open(ptr) + o = json.loads(r) + person_data.apply_custom(o) + except Exception as e: pass return person_data