mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-20 07:05:17 +08:00
remove most profile loading/storing logic, since this will be handled by the token file
This commit is contained in:
@@ -39,6 +39,7 @@ from blockstack_client import user as user_db
|
||||
from .logger import get_logger
|
||||
from .constants import USER_ZONEFILE_TTL, CONFIG_PATH, BLOCKSTACK_TEST, BLOCKSTACK_DEBUG
|
||||
|
||||
from .token_file import token_file_parse, token_file_update_profile, token_file_get, token_file_put
|
||||
from .zonefile import get_name_zonefile
|
||||
from .keys import get_data_privkey_info
|
||||
from .schemas import *
|
||||
@@ -47,306 +48,71 @@ from .constants import BLOCKSTACK_REQUIRED_STORAGE_DRIVERS_WRITE
|
||||
|
||||
log = get_logger()
|
||||
|
||||
|
||||
def set_profile_timestamp(profile, now=None):
|
||||
def get_profile(name, **kw):
|
||||
"""
|
||||
Set the profile's timestamp to now
|
||||
"""
|
||||
now = time.time() if now is None else now
|
||||
profile['timestamp'] = now
|
||||
|
||||
return profile
|
||||
|
||||
|
||||
def get_profile_timestamp(profile):
|
||||
"""
|
||||
Get profile timestamp
|
||||
"""
|
||||
return profile['timestamp']
|
||||
|
||||
|
||||
def load_legacy_user_profile(name, expected_hash):
|
||||
"""
|
||||
Load a legacy user profile, and convert it into
|
||||
the new zonefile-esque profile format that can
|
||||
be serialized into a JWT.
|
||||
|
||||
Verify that the profile hashses to the above expected hash
|
||||
"""
|
||||
|
||||
# fetch...
|
||||
storage_host = 'onename.com'
|
||||
assert name.endswith('.id')
|
||||
|
||||
name_without_namespace = '.'.join(name.split('.')[:-1])
|
||||
storage_path = '/{}.json'.format(name_without_namespace)
|
||||
|
||||
try:
|
||||
req = httplib.HTTPConnection(storage_host)
|
||||
resp = req.request('GET', storage_path)
|
||||
data = resp.read()
|
||||
except Exception as e:
|
||||
log.error('Failed to fetch http://{}/{}: {}'.format(storage_host, storage_path, e))
|
||||
return None
|
||||
|
||||
try:
|
||||
data_json = json.loads(data)
|
||||
except Exception as e:
|
||||
log.error('Unparseable profile data')
|
||||
return None
|
||||
|
||||
data_hash = storage.get_blockchain_compat_hash(data_json)
|
||||
if expected_hash != data_hash:
|
||||
log.error('Hash mismatch: expected {}, got {}'.format(expected_hash, data_hash))
|
||||
return None
|
||||
|
||||
assert blockstack_profiles.is_profile_in_legacy_format(data_json)
|
||||
new_profile = blockstack_profiles.get_person_from_legacy_format(data_json)
|
||||
return new_profile
|
||||
|
||||
|
||||
|
||||
def put_profile(name, new_profile, blockchain_id=None, user_data_privkey=None, user_zonefile=None,
|
||||
proxy=None, wallet_keys=None, required_drivers=None, config_path=CONFIG_PATH):
|
||||
"""
|
||||
Set the new profile data. CLIENTS SHOULD NOT CALL THIS METHOD DIRECTLY.
|
||||
|
||||
if user_data_privkey is given, then wallet_keys does not need to be given.
|
||||
|
||||
Return {'status: True} on success
|
||||
Return {'error': ...} on failure.
|
||||
"""
|
||||
|
||||
ret = {}
|
||||
|
||||
proxy = get_default_proxy() if proxy is None else proxy
|
||||
config = proxy.conf
|
||||
|
||||
# deduce storage drivers
|
||||
required_storage_drivers = None
|
||||
if required_drivers is not None:
|
||||
required_storage_drivers = required_drivers
|
||||
else:
|
||||
required_storage_drivers = config.get('storage_drivers_required_write', None)
|
||||
if required_storage_drivers is not None:
|
||||
required_storage_drivers = required_storage_drivers.split(',')
|
||||
else:
|
||||
required_storage_drivers = config.get('storage_drivers', '').split(',')
|
||||
|
||||
# deduce private key
|
||||
if user_data_privkey is None:
|
||||
user_data_privkey = get_data_privkey_info(user_zonefile, wallet_keys=wallet_keys, config_path=config_path)
|
||||
if json_is_error(user_data_privkey):
|
||||
log.error("Failed to get data private key: {}".format(user_data_privkey['error']))
|
||||
return {'error': 'No data key defined'}
|
||||
|
||||
profile_payload = copy.deepcopy(new_profile)
|
||||
profile_payload = set_profile_timestamp(profile_payload)
|
||||
|
||||
if BLOCKSTACK_DEBUG:
|
||||
# NOTE: don't calculate this string unless we're actually debugging...
|
||||
log.debug('Save updated profile for "{}" to {} at {} by {}'.format(
|
||||
name, ','.join(required_storage_drivers), get_profile_timestamp(profile_payload), get_pubkey_hex(user_data_privkey))
|
||||
)
|
||||
|
||||
rc = storage.put_mutable_data(
|
||||
name, profile_payload, data_privkey=user_data_privkey,
|
||||
required=required_storage_drivers,
|
||||
profile=True, blockchain_id=blockchain_id
|
||||
)
|
||||
|
||||
if rc:
|
||||
ret['status'] = True
|
||||
else:
|
||||
ret['error'] = 'Failed to update profile'
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def delete_profile(blockchain_id, user_data_privkey=None, user_zonefile=None,
|
||||
proxy=None, wallet_keys=None):
|
||||
"""
|
||||
Delete profile data. CLIENTS SHOULD NOT CALL THIS DIRECTLY
|
||||
Return {'status: True} on success
|
||||
Return {'error': ...} on failure.
|
||||
"""
|
||||
|
||||
ret = {}
|
||||
|
||||
proxy = get_default_proxy() if proxy is None else proxy
|
||||
config = proxy.conf
|
||||
|
||||
# deduce private key
|
||||
if user_data_privkey is None:
|
||||
user_data_privkey = get_data_privkey_info(user_zonefile, wallet_keys=wallet_keys, config_path=proxy.conf['path'])
|
||||
if json_is_error(user_data_privkey):
|
||||
log.error("Failed to get data private key: {}".format(user_data_privkey['error']))
|
||||
return {'error': 'No data key defined'}
|
||||
|
||||
rc = storage.delete_mutable_data(blockchain_id, user_data_privkey)
|
||||
if rc:
|
||||
ret['status'] = True
|
||||
else:
|
||||
ret['error'] = 'Failed to update profile'
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def get_profile(name, zonefile_storage_drivers=None, profile_storage_drivers=None,
|
||||
proxy=None, user_zonefile=None, name_record=None,
|
||||
include_name_record=False, include_raw_zonefile=False, use_zonefile_urls=True,
|
||||
use_legacy=False, use_legacy_zonefile=True, decode_profile=True):
|
||||
"""
|
||||
Given a name, look up an associated profile.
|
||||
Do so by first looking up the zonefile the name points to,
|
||||
and then loading the profile from that zonefile's public key.
|
||||
|
||||
Notes on backwards compatibility (activated if use_legacy=True and use_legacy_zonefile=True):
|
||||
|
||||
* (use_legacy=True) If the user's zonefile is really a legacy profile from Onename, then
|
||||
the profile returned will be the converted legacy profile. The returned zonefile will still
|
||||
be a legacy profile, however.
|
||||
The caller can check this and perform the conversion automatically.
|
||||
|
||||
* (use_legacy_zonefile=True) If the name points to a current zonefile that does not have a
|
||||
data public key, then the owner address of the name will be used to verify
|
||||
the profile's authenticity.
|
||||
|
||||
Returns {'status': True, 'profile': profile, 'zonefile': zonefile} on success.
|
||||
* If include_name_record is True, then include 'name_record': name_record with the user's blockchain information
|
||||
* If include_raw_zonefile is True, then include 'raw_zonefile': raw_zonefile with unparsed zone file
|
||||
Legacy compatibility method for get_token_file().
|
||||
Wraps get_token_file, and if a token file is successfully resolved, returns
|
||||
{'status': True,
|
||||
'profile': profile,
|
||||
'zonefile': zonefile,
|
||||
'token_file': token_file,
|
||||
'raw_zonefile': unparsed zone file
|
||||
'token_file': actual token file (if present)
|
||||
'legacy': whether or not the profile was legacy}
|
||||
|
||||
Returns {'error': ...} on error
|
||||
"""
|
||||
res = token_file_get(name, **kw)
|
||||
if 'error' in res:
|
||||
return res
|
||||
|
||||
proxy = get_default_proxy() if proxy is None else proxy
|
||||
|
||||
raw_zonefile = None
|
||||
if user_zonefile is None:
|
||||
user_zonefile = get_name_zonefile(
|
||||
name, proxy=proxy,
|
||||
name_record=name_record, include_name_record=True,
|
||||
storage_drivers=zonefile_storage_drivers,
|
||||
include_raw_zonefile=include_raw_zonefile,
|
||||
allow_legacy=True
|
||||
)
|
||||
|
||||
if 'error' in user_zonefile:
|
||||
return user_zonefile
|
||||
|
||||
raw_zonefile = None
|
||||
if include_raw_zonefile:
|
||||
raw_zonefile = user_zonefile.pop('raw_zonefile')
|
||||
|
||||
user_zonefile = user_zonefile['zonefile']
|
||||
|
||||
# is this really a legacy profile?
|
||||
if blockstack_profiles.is_profile_in_legacy_format(user_zonefile):
|
||||
if not use_legacy:
|
||||
return {'error': 'Profile is in legacy format'}
|
||||
|
||||
# convert it
|
||||
log.debug('Converting legacy profile to modern profile')
|
||||
user_profile = blockstack_profiles.get_person_from_legacy_format(user_zonefile)
|
||||
|
||||
elif not user_db.is_user_zonefile(user_zonefile):
|
||||
if not use_legacy:
|
||||
return {'error': 'Name zonefile is non-standard'}
|
||||
|
||||
# not a legacy profile, but a custom profile
|
||||
log.debug('Using custom legacy profile')
|
||||
user_profile = copy.deepcopy(user_zonefile)
|
||||
token_file = res['token_file']
|
||||
zonefile = res['zonefile']
|
||||
|
||||
profile = None
|
||||
legacy = False
|
||||
if res.get('legacy_profile') is not None:
|
||||
profile = res['legacy_profile']
|
||||
legacy = True
|
||||
|
||||
else:
|
||||
# get user's data public key
|
||||
data_address, owner_address = None, None
|
||||
profile = token_file['profile']
|
||||
|
||||
raw_zonefile = res.get('raw_zonefile')
|
||||
name_record = res.get('name_record')
|
||||
token_file = res.get('token_file')
|
||||
|
||||
try:
|
||||
user_data_pubkey = user_db.user_zonefile_data_pubkey(user_zonefile)
|
||||
if user_data_pubkey is not None:
|
||||
user_data_pubkey = str(user_data_pubkey)
|
||||
data_address = keylib.ECPublicKey(user_data_pubkey).address()
|
||||
|
||||
except ValueError:
|
||||
# multiple keys defined; we don't know which one to use
|
||||
user_data_pubkey = None
|
||||
|
||||
if not use_legacy_zonefile and user_data_pubkey is None:
|
||||
# legacy zonefile without a data public key
|
||||
return {'error': 'Name zonefile is missing a public key'}
|
||||
|
||||
# find owner address
|
||||
if name_record is None:
|
||||
name_record = get_name_blockchain_record(name, proxy=proxy)
|
||||
if name_record is None or 'error' in name_record:
|
||||
log.error('Failed to look up name record for "{}"'.format(name))
|
||||
return {'error': 'Failed to look up name record'}
|
||||
|
||||
assert 'address' in name_record.keys(), json.dumps(name_record, indent=4, sort_keys=True)
|
||||
owner_address = name_record['address']
|
||||
|
||||
# get user's data public key from the zonefile
|
||||
urls = None
|
||||
if use_zonefile_urls and user_zonefile is not None:
|
||||
urls = user_db.user_zonefile_urls(user_zonefile)
|
||||
|
||||
user_profile = storage.get_mutable_data(
|
||||
name, user_data_pubkey, blockchain_id=name,
|
||||
data_address=data_address, owner_address=owner_address,
|
||||
urls=urls, drivers=profile_storage_drivers, decode=decode_profile,
|
||||
)
|
||||
|
||||
if user_profile is None or json_is_error(user_profile):
|
||||
if user_profile is None:
|
||||
log.error('no user profile for {}'.format(name))
|
||||
else:
|
||||
log.error('failed to load profile for {}: {}'.format(name, user_profile['error']))
|
||||
|
||||
return {'error': 'Failed to load user profile'}
|
||||
|
||||
# finally, if the caller asked for the name record, and we didn't get a chance to look it up,
|
||||
# then go get it.
|
||||
ret = {
|
||||
'status': True,
|
||||
'profile': user_profile,
|
||||
'zonefile': user_zonefile
|
||||
'profile': profile,
|
||||
'zonefile': zonefile,
|
||||
'raw_zonefile': raw_zonefile,
|
||||
'name_record': name_record,
|
||||
'token_file': token_file,
|
||||
'legacy': legacy
|
||||
}
|
||||
|
||||
if include_name_record:
|
||||
if name_record is None:
|
||||
name_record = get_name_blockchain_record(name, proxy=proxy)
|
||||
|
||||
if name_record is None or 'error' in name_record:
|
||||
log.error('Failed to look up name record for "{}"'.format(name))
|
||||
return {'error': 'Failed to look up name record'}
|
||||
|
||||
ret['name_record'] = name_record
|
||||
|
||||
if include_raw_zonefile:
|
||||
if raw_zonefile is not None:
|
||||
ret['raw_zonefile'] = raw_zonefile
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _get_person_profile(name, proxy=None):
|
||||
"""
|
||||
Get the person's zonefile and profile.
|
||||
Handle legacy zonefiles, but not legacy profiles.
|
||||
Return {'profile': ..., 'zonefile': ..., 'person': ...} on success
|
||||
Get the person's profile.
|
||||
Works for raw profiles, and for profiles within token files.
|
||||
Only works if the profile is a Persona.
|
||||
|
||||
Return {'profile': ..., 'person': ...} on success
|
||||
Return {'error': ...} on error
|
||||
"""
|
||||
|
||||
res = get_profile(name, proxy=proxy, use_legacy_zonefile=True)
|
||||
res = get_profile(name, proxy=proxy)
|
||||
if 'error' in res:
|
||||
return {'error': 'Failed to load zonefile: {}'.format(zonefile['error'])}
|
||||
return {'error': 'Failed to load profile: {}'.format(res['error'])}
|
||||
|
||||
if res['legacy']:
|
||||
return {'error': 'Failed to load profile: legacy format'}
|
||||
|
||||
profile = res.pop('profile')
|
||||
zonefile = res.pop('zonefile')
|
||||
|
||||
if blockstack_profiles.is_profile_in_legacy_format(profile):
|
||||
return {'error': 'Legacy profile'}
|
||||
|
||||
person = None
|
||||
try:
|
||||
person = blockstack_profiles.Person(profile)
|
||||
@@ -357,26 +123,25 @@ def _get_person_profile(name, proxy=None):
|
||||
return {'profile': profile, 'zonefile': zonefile, 'person': person}
|
||||
|
||||
|
||||
def _save_person_profile(name, zonefile, profile, wallet_keys, user_data_privkey=None, blockchain_id=None, proxy=None, config_path=CONFIG_PATH):
|
||||
def _save_person_profile(name, profile, signing_private_key, blockchain_id=None, proxy=None, config_path=CONFIG_PATH):
|
||||
"""
|
||||
Save a person's profile, given information fetched with _get_person_profile.
|
||||
Return {'status': True} on success
|
||||
Return {'error': ...} on error
|
||||
"""
|
||||
|
||||
conf = get_config(config_path)
|
||||
assert conf
|
||||
|
||||
required_storage_drivers = conf.get(
|
||||
'storage_drivers_required_write',
|
||||
BLOCKSTACK_REQUIRED_STORAGE_DRIVERS_WRITE
|
||||
)
|
||||
required_storage_drivers = conf.get('storage_drivers_required_write', BLOCKSTACK_REQUIRED_STORAGE_DRIVERS_WRITE)
|
||||
required_storage_drivers = required_storage_drivers.split()
|
||||
|
||||
res = put_profile(name, profile, user_zonefile=zonefile,
|
||||
wallet_keys=wallet_keys, user_data_privkey=user_data_privkey, proxy=proxy,
|
||||
required_drivers=required_storage_drivers, blockchain_id=name,
|
||||
config_path=config_path )
|
||||
res = token_file_update_profile(name, profile, signing_private_key)
|
||||
if 'error' in res:
|
||||
return res
|
||||
|
||||
new_token_file = res['token_file']
|
||||
res = token_file_put(name, new_token_file, signing_private_key, proxy=proxy, required_drivers=required_storage_drivers, config_path=config_path)
|
||||
return res
|
||||
|
||||
|
||||
@@ -395,7 +160,6 @@ def profile_list_accounts(name, proxy=None):
|
||||
return name_info
|
||||
|
||||
profile = name_info.pop('profile')
|
||||
zonefile = name_info.pop('zonefile')
|
||||
person = name_info.pop('person')
|
||||
|
||||
accounts = []
|
||||
@@ -493,7 +257,7 @@ def profile_patch_account(cur_profile, service, identifier, content_url, extra_d
|
||||
return profile
|
||||
|
||||
|
||||
def profile_put_account(blockchain_id, service, identifier, content_url, extra_data, wallet_keys, user_data_privkey=None, config_path=CONFIG_PATH, proxy=None):
|
||||
def profile_put_account(blockchain_id, service, identifier, content_url, extra_data, signing_private_key, config_path=CONFIG_PATH, proxy=None):
|
||||
"""
|
||||
Save a new account to a profile.
|
||||
Return {'status': True, 'replaced': True/False} on success
|
||||
@@ -507,19 +271,18 @@ def profile_put_account(blockchain_id, service, identifier, content_url, extra_d
|
||||
if 'error' in person_info:
|
||||
return person_info
|
||||
|
||||
zonefile = person_info.pop('zonefile')
|
||||
profile = person_info.pop('profile')
|
||||
profile = profile_patch_account(profile, service, identifier, content_url, extra_data)
|
||||
|
||||
# save
|
||||
result = _save_person_profile(blockchain_id, zonefile, profile, wallet_keys, user_data_privkey=user_data_privkey, blockchain_id=blockchain_id, proxy=proxy, config_path=config_path)
|
||||
result = _save_person_profile(blockchain_id, profile, signing_private_key, blockchain_id=blockchain_id, proxy=proxy, config_path=config_path)
|
||||
if 'error' in result:
|
||||
return result
|
||||
|
||||
return {'status': True}
|
||||
|
||||
|
||||
def profile_delete_account(blockchain_id, service, identifier, wallet_keys, user_data_privkey=None, config_path=CONFIG_PATH, proxy=None):
|
||||
def profile_delete_account(blockchain_id, service, identifier, signing_private_key, config_path=CONFIG_PATH, proxy=None):
|
||||
"""
|
||||
Delete an account, given the blockchain ID, service, and identifier
|
||||
Return {'status': True} on success
|
||||
@@ -530,7 +293,6 @@ def profile_delete_account(blockchain_id, service, identifier, wallet_keys, user
|
||||
if 'error' in person_info:
|
||||
return person_info
|
||||
|
||||
zonefile = person_info['zonefile']
|
||||
profile = person_info['profile']
|
||||
if not profile.has_key('account'):
|
||||
# nothing to do
|
||||
@@ -553,7 +315,7 @@ def profile_delete_account(blockchain_id, service, identifier, wallet_keys, user
|
||||
if not found:
|
||||
return {'error': 'No such account'}
|
||||
|
||||
result = _save_person_profile(blockchain_id, zonefile, profile, wallet_keys, user_data_privkey=user_data_privkey, blockchain_id=blockchain_id, proxy=proxy, config_path=config_path)
|
||||
result = _save_person_profile(blockchain_id, profile, signing_private_key, blockchain_id=blockchain_id, proxy=proxy, config_path=config_path)
|
||||
if 'error' in result:
|
||||
return result
|
||||
|
||||
|
||||
Reference in New Issue
Block a user