mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-10 16:56:34 +08:00
335 lines
10 KiB
Python
335 lines
10 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Blockstack-client
|
|
~~~~~
|
|
copyright: (c) 2014-2015 by Halfmoon Labs, Inc.
|
|
copyright: (c) 2016 by Blockstack.org
|
|
|
|
This file is part of Blockstack-client.
|
|
|
|
Blockstack-client is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Blockstack-client is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
You should have received a copy of the GNU General Public License
|
|
along with Blockstack-client. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import json
|
|
import time
|
|
import copy
|
|
import blockstack_profiles
|
|
import httplib
|
|
import virtualchain
|
|
import jsonschema
|
|
import virtualchain
|
|
from virtualchain.lib.ecdsalib import *
|
|
import keylib
|
|
|
|
from proxy import *
|
|
from blockstack_client import storage
|
|
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 *
|
|
from config import get_config
|
|
from constants import BLOCKSTACK_REQUIRED_STORAGE_DRIVERS_WRITE
|
|
|
|
log = get_logger()
|
|
|
|
def get_profile(name, **kw):
|
|
"""
|
|
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
|
|
|
|
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:
|
|
profile = res['profile']
|
|
|
|
raw_zonefile = res.get('raw_zonefile')
|
|
name_record = res.get('name_record')
|
|
token_file = res.get('token_file')
|
|
|
|
ret = {
|
|
'status': True,
|
|
'profile': profile,
|
|
'zonefile': zonefile,
|
|
'raw_zonefile': raw_zonefile,
|
|
'name_record': name_record,
|
|
'token_file': token_file,
|
|
'legacy': legacy
|
|
}
|
|
|
|
return ret
|
|
|
|
|
|
def _get_person_profile(name, proxy=None):
|
|
"""
|
|
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)
|
|
if 'error' in res:
|
|
return {'error': 'Failed to load profile: {}'.format(res['error'])}
|
|
|
|
if res['legacy']:
|
|
return {'error': 'Failed to load profile: legacy format'}
|
|
|
|
token_file = res.pop('token_file')
|
|
profile = res.pop('profile')
|
|
person = None
|
|
try:
|
|
person = blockstack_profiles.Person(profile)
|
|
except Exception as e:
|
|
log.exception(e)
|
|
return {'error': 'Failed to parse profile data into a Person record'}
|
|
|
|
return {'profile': profile, 'person': person, 'token_file': token_file}
|
|
|
|
|
|
def _save_person_profile(name, cur_token_file, 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 = required_storage_drivers.split()
|
|
|
|
res = token_file_update_profile(cur_token_file, 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
|
|
|
|
|
|
def profile_list_accounts(name, proxy=None):
|
|
"""
|
|
Get the list of accounts in a name's Person-formatted profile.
|
|
Return {'accounts': ...} on success
|
|
Return {'error': ...} on error
|
|
"""
|
|
|
|
if proxy is None:
|
|
proxy = get_default_proxy()
|
|
|
|
name_info = _get_person_profile(name, proxy=proxy)
|
|
if 'error' in name_info:
|
|
return name_info
|
|
|
|
profile = name_info.pop('profile')
|
|
person = name_info.pop('person')
|
|
|
|
person_accounts = []
|
|
if hasattr(person, 'account'):
|
|
person_accounts = person.account
|
|
|
|
accounts = []
|
|
for acct in person_accounts:
|
|
try:
|
|
jsonschema.validate(acct, PROFILE_ACCOUNT_SCHEMA)
|
|
accounts.append(acct)
|
|
except jsonschema.ValidationError:
|
|
continue
|
|
|
|
return {'accounts': accounts}
|
|
|
|
|
|
def profile_get_account(blockchain_id, service, identifier, config_path=CONFIG_PATH, proxy=None):
|
|
"""
|
|
Get an account. The first hit is returned.
|
|
Return {'status': True, 'account': ...} on success
|
|
Return {'error': ...} on error
|
|
"""
|
|
|
|
account_info = profile_list_accounts(blockchain_id, proxy=proxy )
|
|
if 'error' in account_info:
|
|
return account_info
|
|
|
|
accounts = account_info['accounts']
|
|
for account in accounts:
|
|
if account['service'] == service and account['identifier'] == identifier:
|
|
return {'status': True, 'account': account}
|
|
|
|
return {'error': 'No such account', 'errno': errno.ENOENT}
|
|
|
|
|
|
def profile_find_accounts(cur_profile, service, identifer):
|
|
"""
|
|
Given an profile, find accounts that match the service and identifier
|
|
Returns a list of accounts on success
|
|
"""
|
|
accounts = []
|
|
for acct in cur_profile.get('account', []):
|
|
try:
|
|
jsonschema.validate(acct, PROFILE_ACCOUNT_SCHEMA)
|
|
if acct['service'] == service and acct['identifier'] == identifier:
|
|
accounts.append(acct)
|
|
|
|
except jsonschema.ValidationError:
|
|
continue
|
|
|
|
return accounts
|
|
|
|
|
|
def profile_patch_account(cur_profile, service, identifier, content_url, extra_data):
|
|
"""
|
|
Patch a given profile to add an account
|
|
Return the new profile
|
|
"""
|
|
profile = copy.deepcopy(cur_profile)
|
|
|
|
# make data
|
|
new_account = {
|
|
'service': service,
|
|
'identifier': identifier,
|
|
}
|
|
|
|
if content_url:
|
|
new_account['contentUrl'] = content_url
|
|
|
|
if extra_data:
|
|
new_account.update(extra_data)
|
|
|
|
if not profile.has_key('account'):
|
|
profile['account'] = []
|
|
|
|
# overwrite existing, if given
|
|
replaced = False
|
|
for i in xrange(0, len(profile['account'])):
|
|
|
|
account = profile['account'][i]
|
|
|
|
try:
|
|
jsonschema.validate(account, PROFILE_ACCOUNT_SCHEMA)
|
|
except jsonschema.ValidationError:
|
|
continue
|
|
|
|
if account['service'] == service and account['identifier'] == identifier:
|
|
profile['account'][i] = new_account
|
|
replaced = True
|
|
break
|
|
|
|
if not replaced:
|
|
profile['account'].append(new_account)
|
|
|
|
return profile
|
|
|
|
|
|
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
|
|
Return {'error': ...} on failure
|
|
"""
|
|
|
|
if proxy is None:
|
|
proxy = get_default_proxy()
|
|
|
|
person_info = _get_person_profile(blockchain_id, proxy=proxy)
|
|
if 'error' in person_info:
|
|
return person_info
|
|
|
|
token_file = person_info['token_file']
|
|
if token_file is None:
|
|
return {'error': 'Name points to raw, legacy profile. Please use the `migrate` command to migrate to the latest profile data format'}
|
|
|
|
profile = person_info.pop('profile')
|
|
profile = profile_patch_account(profile, service, identifier, content_url, extra_data)
|
|
|
|
# save
|
|
result = _save_person_profile(blockchain_id, token_file, 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, signing_private_key, config_path=CONFIG_PATH, proxy=None):
|
|
"""
|
|
Delete an account, given the blockchain ID, service, and identifier
|
|
Return {'status': True} on success
|
|
Return {'error': ...} on error
|
|
"""
|
|
|
|
person_info = _get_person_profile(blockchain_id, proxy=proxy)
|
|
if 'error' in person_info:
|
|
return person_info
|
|
|
|
profile = person_info['profile']
|
|
if not profile.has_key('account'):
|
|
# nothing to do
|
|
return {'error': 'No such account'}
|
|
|
|
token_file = person_info['token_file']
|
|
if token_file is None:
|
|
return {'error': 'Name points to raw, legacy profile. Please use the `migrate` command to migrate to the latest profile data format'}
|
|
|
|
found = False
|
|
for i in xrange(0, len(profile['account'])):
|
|
account = profile['account'][i]
|
|
|
|
try:
|
|
jsonschema.validate(account, PROFILE_ACCOUNT_SCHEMA)
|
|
except jsonschema.ValidationError:
|
|
continue
|
|
|
|
if account['service'] == service and account['identifier'] == identifier:
|
|
profile['account'].pop(i)
|
|
found = True
|
|
break
|
|
|
|
if not found:
|
|
return {'error': 'No such account'}
|
|
|
|
result = _save_person_profile(blockchain_id, token_file, profile, signing_private_key, blockchain_id=blockchain_id, proxy=proxy, config_path=config_path)
|
|
if 'error' in result:
|
|
return result
|
|
|
|
return {'status': True}
|
|
|
|
|