def systest_auth(requesting_app, providing_app, user): """ Ensuring that an incorrectly signed request is rejected. """ def broken(d, field): d[field] = 'unlucky' + d[field][7:] return d log.info(f'{requesting_app} is requesting data from {providing_app}') body = {'users': [user], 'data_id': 'request_hash'} app_config = demo_config['demo_apps'][providing_app] port = app_config['rpc_server_port'] eos_client = get_eos_rpc_client() mother = UnificationMother(eos_client, providing_app, get_cleos(), get_ipfs_client()) provider_obj = Provider(providing_app, 'https', mother) encoded_password = demo_config['system'][requesting_app]['password'] ks = UnificationKeystore(encoded_password, app_name=requesting_app, keystore_path=Path('data/keys')) payload = bundle(ks, requesting_app, provider_obj.name, body, 'Success') payload = broken(payload, 'signature') r = provider_obj.post('data_request', payload) assert r.status_code == 401
def run_test_mother(self, app, demo_apps): print("Contacting MOTHER FOR: ", app) eos_client = Client(nodes=[self.cleos.get_nodeos_url()]) um = UnificationMother(eos_client, app, get_cleos(), get_ipfs_client()) print("Valid app: ", um.valid_app()) assert um.valid_app() is True print("UApp SC Hash in MOTHER: ", um.get_hash_in_mother()) print("Deployed UApp SC hash: ", um.get_deployed_contract_hash()) assert um.get_hash_in_mother() == um.get_deployed_contract_hash() print("Valid Code: ", um.valid_code()) assert um.valid_code() is True print("Signed by MOTHER: ", um.signed_by_mother()) assert um.signed_by_mother() is True print("RPC IP: ", um.get_haiku_rpc_ip()) assert um.get_haiku_rpc_ip() == demo_apps[app]['rpc_server'] print("RPC Port: ", um.get_haiku_rpc_port()) assert int(um.get_haiku_rpc_port()) == int( demo_apps[app]['rpc_server_port']) print("RPC Server: ", um.get_haiku_rpc_server()) print("-----------------------------------")
def view(provider, request_hash): """ Read data stored locally from an Data Provider for a particular user. \b :param provider: The app name of the data provider. :param request_hash: The particular piece of data in concern. :return: """ requesting_app = os.environ['app_name'] password = os.environ['keystore'] eos_client = get_eos_rpc_client() mother = UnificationMother(eos_client, provider, get_cleos(), get_ipfs_client()) provider_obj = Provider(provider, 'https', mother) req_hash = f'request-{request_hash}' click.echo(f'App {requesting_app} is reading ingested data from ' f'{provider_obj.name}') encoded_password = str.encode(password) keystore = UnificationKeystore(encoded_password) client = HaikuDataClient(keystore) data = client.read_data_from_store(provider_obj, req_hash) click.echo(json.loads(data))
def __init__(self, eos_client, uapp_contract_acc, requesting_app, users): """ :param users: A list of users we want to obtain data for. If an empty list is provided, then data for all permissible users are provided. """ self.__uapp_contract_acc = uapp_contract_acc self.__requesting_app = requesting_app self.__haiku_conf = UnificationConfig() self.__my_mother = UnificationMother(eos_client, uapp_contract_acc, get_cleos(), get_ipfs_client()) self.__my_uapp_sc = UnificationUapp(eos_client, uapp_contract_acc) self.__my_lookup = UnificationLookup(default_lookup_db()) self.__users = None if len(users) == 0 else users self.__my_db_schemas = self.__my_uapp_sc.get_all_db_schemas() self.__db_schema_maps = {} self.__granted = [] self.__granted_field_lookup = {} self.__revoked = [] self.__raw_data = None self.__native_user_meta = self.__my_lookup.get_native_user_meta() for pkey, db_schema in self.__my_db_schemas.items(): schema_map = self.__my_lookup.get_schema_map(pkey) self.__db_schema_maps[pkey] = schema_map self.__generate_data()
def __check_app_is_valid(self): """ Call the MOTHER Smart Contract, and check if the requesting_app is both a verified app, and that it's smart contract code is valid (by checking the code's hash). """ um = UnificationMother(self.__eosClient, self.__app_to_validate, get_cleos(), get_ipfs_client()) self.__is_valid_app = um.valid_app() self.__is_valid_code = um.valid_code() self.__signed_by_mother = um.signed_by_mother()
def balance(user, password): """ Get UND balance for an account \b :param user: The EOS user account name. :param password: The EOS user account's wallet password. """ cleos = get_cleos() cleos.unlock_wallet(user, password) my_balance = get_balance(user) cleos.lock_wallet(user) click.echo(bold(f'{user} Balance: {my_balance}'))
def fetch(provider, request_hash, user): """ Fetch data from an App to this App in an Enterprise/DSP/B2B environment, where data request is agreed external to the UApp Store. \b :param provider: The app name of the data provider. :param request_hash: The particular piece of data in concern. :param user: Obtain data for a specific user EOS user account. :return: """ requesting_app = os.environ['app_name'] password = os.environ['keystore'] # Write the data request to the Consumer's UApp smart contract eos_client = get_eos_rpc_client() mother = UnificationMother(eos_client, provider, get_cleos(), get_ipfs_client()) provider = Provider(provider, 'https', mother) req_hash = f'request-{request_hash}' suffix = 'for all users' if user is None else f'for {user}' click.echo(f'App {requesting_app} is requesting data from {provider}' f' {suffix}') encoded_password = str.encode(password) keystore = UnificationKeystore(encoded_password) # tmp - get the price for the transfer from Schema[0] in provider's UApp SC # This will possibly be determined externally as part of the B2B agreement provider_uapp_sc = UnificationUapp(eos_client, provider.name) db_schema = provider_uapp_sc.get_db_schema_by_pkey( 0) # tmp - only 1 schema sched_price = db_schema['price_sched'] # initiate request in Consumer's UApp SC consumer_uapp_sc = UnificationUapp(eos_client, requesting_app) latest_req_id = consumer_uapp_sc.init_data_request(provider.name, "0", "0", sched_price) client = HaikuDataClient(keystore) data_path = client.make_data_request(requesting_app, provider, user, req_hash, latest_req_id) click.echo(f'Data written to: {data_path}') click.echo(f'View using: haiku view {provider.name} {request_hash}')
def __init__(self, eos_rpc_client, uapp_contract_acc): """ :param eos_rpc_client: EOS RPC Client :param uapp_contract_acc: the eos account name of the app for which the class will retrieve data from the UApp smart contract """ self.__ipfs_perm_table = "userperms" self.__rsa_pub_key_table = "rsapubkey" self.__db_schema_table = "dataschemas" self.__data_requests_table = "datareqs" self.__uapp_contract_acc = uapp_contract_acc self.__eos_rpc_client = eos_rpc_client # Todo: Migrate from cleos command line to EOS RPC API self.__cleos = get_cleos()
def systest_smart_contract_mother(): log.info('Running systest smart contract MOTHER') d_conf = json.loads(Path('data/demo_config.json').read_text()) appnames = ['app1', 'app2', 'app3'] d_apps = d_conf['demo_apps'] conf = UnificationConfig() eos_client = Client( nodes=[f"http://{conf['eos_rpc_ip']}:{conf['eos_rpc_port']}"]) for appname in appnames: log.info("------------------------------------------") app_data = d_apps[appname] log.info(f"Contacting MOTHER for {app_data['eos_sc_account']}") mother = UnificationMother(eos_client, app_data['eos_sc_account'], get_cleos(), get_ipfs_client()) log.info("App is Valid") log.info("Expecting: True") log.info(f"Actual - MOTHER: {mother.valid_app()}") assert mother.valid_app() is True log.info("App Code is Valid") log.info("Expecting: True") log.info(f"Actual - MOTHER: {mother.valid_code()}") assert mother.valid_app() is True log.info("Code Hash") log.info( f"Expecting - config.json: {mother.get_deployed_contract_hash()}") log.info(f"Actual - MOTHER: {mother.get_hash_in_mother()}") assert (mother.get_deployed_contract_hash() == mother.get_hash_in_mother()) is True log.info("RPC IP") log.info(f"Expecting - config.json: {app_data['rpc_server']}") log.info(f"Actual - MOTHER: {mother.get_haiku_rpc_ip()}") assert (app_data['rpc_server'] == mother.get_haiku_rpc_ip()) is True log.info("RPC Port") log.info(f"Expecting - config.json: {app_data['rpc_server_port']}") log.info(f"Actual - MOTHER: {mother.get_haiku_rpc_port()}") assert (int(app_data['rpc_server_port']) == int( mother.get_haiku_rpc_port())) is True log.info("------------------------------------------")
def transfer(from_acc, to_acc, amount, password): """ Quick UND transfer method. \b :param from_acc: The EOS user account name SENDING the UNDs. :param to_acc: The EOS user account name RECEIVING the UNDs. :param amount: amount to send. :param password: The SENDING EOS user account's wallet password. """ # TODO: need to make the babel client initialised, and locked to a user amt = "{0:.4f}".format(round(float(amount), 4)) click.echo(f"{bold(from_acc)} is transferring {bold(amt)} UND" f"to {bold(to_acc)}:") my_balance = get_balance(from_acc) click.echo(bold(f'{from_acc} Old Balance: {my_balance}')) their_balance = get_balance(to_acc) click.echo(bold(f'{to_acc} Old Balance: {their_balance}')) cleos = get_cleos() cleos.unlock_wallet(from_acc, password) d = { 'from': from_acc, 'to': to_acc, 'quantity': f'{amt} UND', 'memo': 'UND transfer' } ret = cleos.run( ['push', 'action', 'unif.token', 'transfer', json.dumps(d), '-p', from_acc]) cleos.lock_wallet(from_acc) stripped = ret.stdout.strip() click.echo(bold(f'Transfer result: {stripped}')) my_balance = get_balance(from_acc) click.echo(bold(f'{from_acc} New Balance: {my_balance}')) their_balance = get_balance(to_acc) click.echo(bold(f'{to_acc} New Balance: {their_balance}'))
def __init__(self, uapp_acc: str, und_amt: int): """ :param uapp_acc: The account name of the payer. :param und_amt: The amount of UND to be processed """ self.__my_uapp_acc = uapp_acc self.__cleos = get_cleos() #TODO: I think I can deprecate the following conf = UnificationConfig() self.__eos_client_pre = [ "/opt/eosio/bin/cleos", "--url", f"http://{conf['eos_rpc_ip']}:{conf['eos_rpc_port']}", "--wallet-url", f"http://{conf['eos_wallet_ip']}:{conf['eos_wallet_port']}" ] self.__und_amt = und_amt
def __request_from_uapp_store(data_request): """ Receives a data request from the UApp Store, and processes the request \b :param data_request: Dict containing request parameters """ requesting_app = os.environ['app_name'] password = os.environ['keystore'] click.echo("Processing request from UApp Store:") click.echo(data_request) eos_client = get_eos_rpc_client() # Write the data request to the Consumer's smart contract uapp_sc = UnificationUapp(eos_client, requesting_app) latest_req_id = uapp_sc.init_data_request(data_request['provider'], data_request['schema_pkey'], "0", data_request['price']) request_hash = f"{data_request['provider']}-{data_request['schema_pkey']}" \ f"-{latest_req_id}.dat" provider_name = data_request['provider'] mother = UnificationMother(eos_client, provider_name, get_cleos(), get_ipfs_client()) provider_obj = Provider(provider_name, 'https', mother) req_hash = f'request-{request_hash}' click.echo(f'App {requesting_app} is requesting data from ' f'{provider_obj.name}') encoded_password = str.encode(password) keystore = UnificationKeystore(encoded_password) client = HaikuDataClient(keystore) data_path = client.make_data_request(requesting_app, provider_obj, None, req_hash, latest_req_id) click.echo(f'Data written to: {data_path}') click.echo(f'View using: haiku view {provider_obj.name} {request_hash}')
def systest_ingest(requesting_app, providing_app, user, balances): log.info(f'Testing Fetch ingestion: {requesting_app} ' f'is requesting data from {providing_app}') request_hash = f'data-request-{providing_app}-{requesting_app}' app_config = demo_config['demo_apps'][providing_app] port = app_config['rpc_server_port'] eos_client = get_eos_rpc_client() mother = UnificationMother(eos_client, providing_app, get_cleos(), get_ipfs_client()) provider_obj = Provider(providing_app, 'https', mother) password = demo_config['system'][requesting_app]['password'] encoded_password = str.encode(password) keystore = UnificationKeystore(encoded_password, app_name=requesting_app, keystore_path=Path('data/keys')) conf = UnificationConfig() eos_client = Client( nodes=[f"http://{conf['eos_rpc_ip']}:{conf['eos_rpc_port']}"]) consumer_uapp_sc = UnificationUapp(eos_client, requesting_app) price_sched = demo_config['demo_apps'][providing_app]['db_schemas'][0][ 'price_sched'] latest_req_id = consumer_uapp_sc.init_data_request(provider_obj.name, "0", "0", price_sched) client = HaikuDataClient(keystore) client.make_data_request(requesting_app, provider_obj, user, request_hash, latest_req_id) client.read_data_from_store(provider_obj, request_hash) # Update the system test record of the balances balances[requesting_app] = balances[requesting_app] - price_sched und_rewards = UndRewards(providing_app, price_sched) balances[providing_app] = (balances[providing_app] + und_rewards.calculate_reward(is_user=False)) return balances
def systest_process_permission_batches(): appnames = ['app1', 'app2', 'app3'] for app_name in appnames: log.debug(f'run systest_process_permission_batches for {app_name}') mother = UnificationMother(get_eos_rpc_client(), app_name, get_cleos(), get_ipfs_client()) provider_obj = Provider(app_name, 'https', mother) password = demo_config['system'][app_name]['password'] encoded_password = str.encode(password) keystore = UnificationKeystore(encoded_password, app_name=app_name, keystore_path=Path('data/keys')) client = HaikuDataClient(keystore) try: client.process_permissions_batch(provider_obj) except Exception as e: log.error(f'systest_process_permission_batches failed: {e}')
def request_permission_change(self, user, app_permission_list, private_key): log.info(f"Process {user} permission change requests") for consumer, providers in app_permission_list.items(): for provider, permissions in providers.items(): granted = permissions['granted'] if granted: fields = permissions['fields'] else: fields = '' schema_id = int(permissions['schema_id']) log.debug(f'request_permission_change ' f'{user} requesting {provider} ' f'update perms for {consumer} ' f'in schema {schema_id}: {granted} {fields}') payload, p_nonce, p_sig = generate_payload( user, private_key, provider, consumer, fields, 'active', schema_id) log.debug(f'request_permission_change payload: ' f'{json.dumps(payload)}') mother = UnificationMother(get_eos_rpc_client(), provider, get_cleos(), get_ipfs_client()) provider_obj = Provider(provider, 'https', mother) r = provider_obj.post('modify_permission', payload) d = r.json() if r.status_code != 200: raise Exception(d['message']) proc_id = d['proc_id'] ret_app = d['app'] log.debug(f"request_permission_change success: " f"{ret_app}: Process ID {proc_id}")
def modify_permission(): conf = app.unification_config try: d = flask.request.get_json() eos_perm = d['eos_perm'] req_sender = d['user'] jwt = d['jwt'] cleos = get_cleos() eos_rpc_client = get_eos_rpc_client() # ToDo: find better way to get public key from EOS account public_key = cleos.get_public_key(req_sender, eos_perm) unif_jwt = UnifJWT(jwt, public_key) issuer = unif_jwt.get_issuer() audience = unif_jwt.get_audience() if audience != conf['uapp_contract']: return error_request_not_me() if req_sender != issuer: return error_request_not_you() payload = unif_jwt.get_payload() # Check field list sent matches fields in metadata schema if len(payload['perms']) > 0: field_list = payload['perms'].split(',') uapp_sc = UnificationUapp(eos_rpc_client, conf['uapp_contract']) db_schema = uapp_sc.get_db_schema_by_pkey(int( payload['schema_id'])) if not db_schema: return generic_error( f"Invalid Metadata Schema ID: {payload['schema_id']}") valid_fields = [f['name'] for f in db_schema['schema']['fields']] for pf in field_list: if pf not in valid_fields: return generic_error( f"Invalid field list: {payload['perms']}") batcher = PermissionBatcher(pb_default_db()) rowid = batcher.add_to_queue(issuer, payload['consumer'], payload['schema_id'], payload['perms'], payload['p_nonce'], payload['p_sig'], public_key) d = {'app': conf['uapp_contract'], 'proc_id': rowid} return flask.jsonify(d), 200 except InvalidJWT as e: return invalid_jwt(e) except InvalidPublicKey as e: return invalid_jwt(e) except JWTSignatureMismatch as e: return invalid_jwt(e) except InvalidSignature: return invalid_response() except Exception as e: logger.exception(e) return generic_error()