def get_datastore_creds(master_data_privkey=None, app_domain=None, app_user_privkey=None, config_path=CONFIG_PATH): """ Get datastore credentials Return {'status': True, 'datastore_privkey': ..., 'datastore_id': ...} on success Return {'error': ...} on error """ assert app_user_privkey is not None or ( master_data_privkey is not None and app_domain is not None ), "Invalid creds: need app_domain and master_data_privkey, or app_user_privkey" if app_user_privkey is None: app_user_privkey = data.datastore_get_privkey(master_data_privkey, app_domain, config_path=CONFIG_PATH) if app_user_privkey is None: return { 'error': 'Failed to load app user private key', 'errno': errno.EPERM } app_user_pubkey = get_pubkey_hex(app_user_privkey) datastore_id = data.datastore_get_id(app_user_pubkey) ret = { 'status': True, 'datastore_privkey': app_user_privkey, 'datastore_pubkey': app_user_pubkey, 'datastore_id': datastore_id } return ret
def app_make_session(blockchain_id, app_public_key, app_domain, methods, app_public_keys, requester_device_id, master_data_privkey, session_lifetime=None, config_path=CONFIG_PATH): """ Make a session JWT for this application. Verify with user private key Sign with master private key Return {'session': session jwt, 'session_token': session token} on success Return {'error': ...} on error """ conf = get_config(path=config_path) assert conf if session_lifetime is None: session_lifetime = conf.get('default_session_lifetime', 1e80) # ysi-storage.js assumes it needs to use an # uncompressed address. let's do that if we need to app_datastore_public_key = keylib.key_formatting.decompress(app_public_key) app_user_id = data.datastore_get_id(app_datastore_public_key) api_endpoint_host = conf.get('api_endpoint_host', DEFAULT_API_HOST) api_endpoint_port = conf.get('api_endpoint_port', DEFAULT_API_PORT) api_endpoint = '{}:{}'.format(api_endpoint_host, api_endpoint_port) ses = { 'version': 1, 'blockchain_id': blockchain_id, 'app_domain': app_domain, 'methods': methods, 'app_public_keys': app_public_keys, 'app_user_id': app_user_id, 'api_endpoint': api_endpoint, 'device_id': requester_device_id, 'storage': { 'classes': classify_storage_drivers(), 'preferences': {} }, 'timestamp': int(time.time()), 'expires': int(time.time() + session_lifetime), } jsonschema.validate(ses, APP_SESSION_SCHEMA) signer = jsontokens.TokenSigner() session_token = signer.sign(ses, master_data_privkey) session = jsontokens.decode_token(session_token) return {'session': session, 'session_token': session_token}
def get_datastore_info(datastore_id=None, app_user_privkey=None, master_data_privkey=None, app_domain=None, config_path=CONFIG_PATH, proxy=None): """ Get information about an account datastore. At least, get the user and account owner. Return {'status': True, 'datastore': ..., 'datastore_id': ..., ['datastore_privkey': ...}] on success. Return {'error': ...} on failure """ datastore_privkey = None if app_user_privkey is not None or (master_data_privkey is not None and app_domain is not None): creds = get_datastore_creds(master_data_privkey, app_domain=app_domain, app_user_privkey=app_user_privkey, config_path=config_path) if 'error' in creds: return creds if datastore_id is not None: assert datastore_id == creds[ 'datastore_id'], "Datastore mismatch: {} != {}".format( datastore_id, creds['datastore_id']) datastore_privkey = creds['datastore_privkey'] datastore_id = creds['datastore_id'] assert datastore_id, 'No datastore ID given' res = data.get_datastore(datastore_id, config_path=config_path, proxy=proxy) if 'error' in res: return res if datastore_privkey is not None: res['datastore_privkey'] = datastore_privkey if datastore_id is not None: res['datastore_id'] = datastore_id else: res['datastore_id'] = data.datastore_get_id(res['datastore']['pubkey']) return res
def app_make_session( blockchain_id, app_private_key, app_domain, methods, app_public_keys, requester_device_id, master_data_privkey, session_lifetime=None, config_path=CONFIG_PATH ): """ Make a session JWT for this application. Verify with user private key Sign with master private key Return {'session': session jwt, 'session_token': session token} on success Return {'error': ...} on error """ conf = get_config(path=config_path) assert conf if session_lifetime is None: session_lifetime = conf.get('default_session_lifetime', 1e80) app_public_key = get_pubkey_hex(app_private_key) app_user_id = data.datastore_get_id(app_public_key) api_endpoint_host = conf.get('api_endpoint_host', DEFAULT_API_HOST) api_endpoint_port = conf.get('api_endpoint_port', DEFAULT_API_PORT) api_endpoint = '{}:{}'.format(api_endpoint_host, api_endpoint_port) ses = { 'version': 1, 'blockchain_id': blockchain_id, 'app_domain': app_domain, 'methods': methods, 'app_public_keys': app_public_keys, 'app_user_id': app_user_id, 'api_endpoint': api_endpoint, 'device_id': requester_device_id, 'storage': { 'classes': classify_storage_drivers(), 'preferences': {} }, 'timestamp': int(time.time()), 'expires': int(time.time() + session_lifetime), } jsonschema.validate(ses, APP_SESSION_SCHEMA) signer = jsontokens.TokenSigner() session_token = signer.sign( ses, master_data_privkey ) session = jsontokens.decode_token(session_token) return {'session': session, 'session_token': session_token}
def app_make_session( app_domain, methods, master_data_privkey_hex, app_user_id=None, app_user_privkey=None, session_lifetime=None, blockchain_ids=None, config_path=CONFIG_PATH ): """ Make a session JWT for this application. Verify with user private key Sign with master private key Return {'session': session jwt, 'session_token': session token} on success Return {'error': ...} on error """ if session_lifetime is None: conf = get_config(path=config_path) assert conf session_lifetime = conf.get('default_session_lifetime', 1e80) if app_user_id is None: if app_user_privkey is None: if master_data_privkey_hex is not None: assert app_domain is not None, "need app domain to derive app key" app_user_privkey = data.datastore_get_privkey(master_data_privkey_hex, app_domain, config_path=config_path) else: # TODO: load from disk raise NotImplemented("Local app user private keys are not supported at this time") app_user_pubkey = get_pubkey_hex(app_user_privkey) app_user_id = data.datastore_get_id(app_user_pubkey) ses = { 'app_domain': app_domain, 'methods': methods, 'app_user_id': app_user_id, 'timestamp': int(time.time()), 'expires': int(time.time() + session_lifetime), } if blockchain_ids is not None: ses['blockchain_ids'] = blockchain_ids jsonschema.validate(ses, APP_SESSION_SCHEMA) signer = jsontokens.TokenSigner() session_token = signer.sign( ses, master_data_privkey_hex ) session = jsontokens.decode_token(session_token) return {'session': session, 'session_token': session_token}
def blockstack_url_fetch(url, proxy=None, config_path=CONFIG_PATH, wallet_keys=None): """ Given a blockstack:// url, fetch its data. If the data is an immutable data url, and the hash is not given, then look up the hash first. If the data is a mutable data url, and the version is not given, then look up the version as well. Data from datastores requires wallet_keys Return {"data": data} on success Return {"error": error message} on error """ mutable = False immutable = False blockchain_id = None data_id = None version = None data_hash = None url_info = blockstack_data_url_parse(url) if url_info is None: return {'error': 'Failed to parse {}'.format(url)} data_id = url_info['data_id'] blockchain_id = url_info['blockchain_id'] url_type = url_info['type'] fields = url_info['fields'] if url_type == 'mutable': datastore_id = fields.get('datastore_id') version = fields.get('version') app_domain = fields.get('app_domain') mutable = True else: data_hash = fields.get('data_hash') immutable = True if mutable: if app_domain is not None: # get from datastore datastore_info = get_datastore_info(datastore_id=datastore_id, config_path=config_path, proxy=proxy) if 'error' in datastore_info: return datastore_info datastore = datastore_info['datastore'] if data.datastore_get_id(datastore['pubkey']) != datastore_id: return {'error': 'Invalid datastore ID'} # file or directory? is_dir = data_id.endswith('/') if is_dir: return data.datastore_listdir(datastore, data_id, config_path=config_path, proxy=proxy) else: return data.datastore_getfile(datastore, data_id, config_path=config_path, proxy=proxy) elif blockchain_id is not None: # get single data if version is not None: return data.get_mutable(data_id, proxy=proxy, ver_min=version, ver_max=version + 1, blockchain_id=blockchain_id, fully_qualified_data_id=True) else: return data.get_mutable(data_id, proxy=proxy, blockchain_id=blockchain_id, fully_qualified_data_id=True) else: return {'error': 'Invalid URL'} else: if data_id is not None: # get single data if data_hash is not None: return data.get_immutable(blockchain_id, data_hash, data_id=data_id, proxy=proxy) else: return data.get_immutable_by_name(blockchain_id, data_id, proxy=proxy) else: # list data return data.list_immutable_data(blockchain_id, proxy=proxy, config_path=config_path)