mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-11 08:37:40 +08:00
resolver now resolves fully qualified names, but defaults to 'id' if no namespace is given
This commit is contained in:
256
api/resolver.py
256
api/resolver.py
@@ -27,25 +27,19 @@ import re
|
||||
import json
|
||||
import collections
|
||||
import logging
|
||||
import xmlrpclib
|
||||
|
||||
from flask import Flask, make_response, jsonify, abort, request
|
||||
from flask import Blueprint
|
||||
from flask_crossdomain import crossdomain
|
||||
|
||||
from time import time
|
||||
from basicrpc import Proxy
|
||||
|
||||
from blockstack_proofs import profile_to_proofs, profile_v3_to_proofs
|
||||
from blockstack_profiles import resolve_zone_file_to_profile
|
||||
from blockstack_profiles import get_token_file_url_from_zone_file
|
||||
from blockstack_profiles import get_profile_from_tokens
|
||||
#from blockstack_profiles import is_profile_in_legacy_format
|
||||
from blockstack_zones import parse_zone_file
|
||||
|
||||
from blockstack_client.proxy import get_name_blockchain_record
|
||||
import blockstack_client.profile
|
||||
|
||||
from blockstack_client.schemas import OP_NAME_PATTERN, OP_NAMESPACE_PATTERN
|
||||
|
||||
from api.utils import cache_control, get_mc_client
|
||||
|
||||
from .config import DEBUG
|
||||
@@ -72,39 +66,6 @@ else:
|
||||
|
||||
mc = get_mc_client()
|
||||
|
||||
def validName(name):
|
||||
""" Return True if valid name
|
||||
"""
|
||||
|
||||
# current regrex doesn't account for .namespace
|
||||
regrex = re.compile('^[a-z0-9_]{1,60}$')
|
||||
|
||||
if regrex.match(name):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def fetch_from_dht(profile_hash):
|
||||
""" Given a @profile_hash fetch full profile JSON
|
||||
"""
|
||||
|
||||
dht_client = Proxy(DHT_MIRROR_IP, DHT_MIRROR_PORT)
|
||||
|
||||
try:
|
||||
dht_resp = dht_client.get(profile_hash)
|
||||
except:
|
||||
#abort(500, "Connection to DHT timed out")
|
||||
return {"error": "Data not saved in DHT yet."}
|
||||
|
||||
dht_resp = dht_resp[0]
|
||||
|
||||
if dht_resp is None:
|
||||
return {"error": "Data not saved in DHT yet."}
|
||||
|
||||
return dht_resp['value']
|
||||
|
||||
|
||||
def fetch_proofs(profile, username, profile_ver=2, refresh=False):
|
||||
""" Get proofs for a profile and:
|
||||
a) check cached entries
|
||||
@@ -134,7 +95,6 @@ def fetch_proofs(profile, username, profile_ver=2, refresh=False):
|
||||
|
||||
return proofs
|
||||
|
||||
|
||||
def is_profile_in_legacy_format(profile):
|
||||
"""
|
||||
Is a given profile JSON object in legacy format?
|
||||
@@ -170,126 +130,48 @@ def is_profile_in_legacy_format(profile):
|
||||
|
||||
return is_in_legacy_format
|
||||
|
||||
|
||||
def parse_uri_from_zone_file(zone_file):
|
||||
|
||||
token_file_url = None
|
||||
zone_file = dict(parse_zone_file(zone_file))
|
||||
|
||||
if isinstance(zone_file["uri"], list) and len(zone_file["uri"]) > 0:
|
||||
|
||||
index = 0
|
||||
while(index < len(zone_file["uri"])):
|
||||
|
||||
record = zone_file["uri"][index]
|
||||
|
||||
if 'name' in record and record['name'] == '_http._tcp':
|
||||
first_uri_record = zone_file["uri"][index]
|
||||
token_file_url = first_uri_record["target"]
|
||||
break
|
||||
|
||||
index += 1
|
||||
|
||||
return token_file_url
|
||||
|
||||
|
||||
def resolve_zone_file_from_rpc(zone_file, owner_address):
|
||||
|
||||
rpc_uri = parse_uri_from_zone_file(zone_file)
|
||||
|
||||
try:
|
||||
uri, fqu = rpc_uri.rsplit('#')
|
||||
except:
|
||||
return None
|
||||
|
||||
try:
|
||||
s = xmlrpclib.ServerProxy(uri, allow_none=True)
|
||||
data = s.get_profile(fqu)
|
||||
except Exception as e:
|
||||
print e
|
||||
|
||||
data = json.loads(data)
|
||||
profile = json.loads(data['profile'])
|
||||
pubkey = profile[0]['parentPublicKey']
|
||||
|
||||
try:
|
||||
profile = get_profile_from_tokens(profile, pubkey)
|
||||
except Exception as e:
|
||||
print e
|
||||
|
||||
return profile
|
||||
|
||||
|
||||
def resolve_zone_file_to_profile(zone_file, address_or_public_key):
|
||||
|
||||
profile = None
|
||||
|
||||
if is_profile_in_legacy_format(zone_file):
|
||||
return zone_file
|
||||
|
||||
try:
|
||||
token_file_url = get_token_file_url_from_zone_file(zone_file)
|
||||
|
||||
r = requests.get(token_file_url)
|
||||
|
||||
profile_token_records = json.loads(r.text)
|
||||
|
||||
profile = get_profile_from_tokens(profile_token_records, address_or_public_key)
|
||||
except Exception as e:
|
||||
|
||||
profile = resolve_zone_file_from_rpc(zone_file, address_or_public_key)
|
||||
|
||||
print profile
|
||||
return profile, None
|
||||
|
||||
|
||||
def format_profile(profile, username, zone_file, refresh=False):
|
||||
def format_profile(profile, fqa, zone_file, refresh=False):
|
||||
""" Process profile data and
|
||||
1) Insert verifications
|
||||
2) Check if profile data is valid JSON
|
||||
"""
|
||||
|
||||
data = {}
|
||||
|
||||
if 'error' in profile:
|
||||
data['profile'] = {}
|
||||
data['error'] = profile['error']
|
||||
data['verifications'] = []
|
||||
data['zone_file'] = zone_file
|
||||
data = {'profile' : profile,
|
||||
'zone_file' : zone_file}
|
||||
|
||||
try:
|
||||
username, ns = fqa.split(".")
|
||||
except:
|
||||
data = {'error' : "Failed to split fqa into name and namespace."}
|
||||
return data
|
||||
if ns != 'id':
|
||||
data['verifications'] = ["No verifications for non-id namespaces."]
|
||||
return data
|
||||
|
||||
if profile is None:
|
||||
data['profile'] = {}
|
||||
|
||||
if error is not None:
|
||||
data['error'] = error
|
||||
else:
|
||||
data['error'] = "Malformed profile data."
|
||||
data['verifications'] = []
|
||||
profile_in_legacy_format = is_profile_in_legacy_format(profile)
|
||||
|
||||
if not profile_in_legacy_format:
|
||||
data['verifications'] = fetch_proofs(data['profile'], username,
|
||||
profile_ver=3, refresh=refresh)
|
||||
else:
|
||||
|
||||
profile_in_legacy_format = is_profile_in_legacy_format(profile)
|
||||
|
||||
if not profile_in_legacy_format:
|
||||
data['profile'] = profile
|
||||
data['verifications'] = fetch_proofs(data['profile'], username,
|
||||
profile_ver=3, refresh=refresh)
|
||||
else:
|
||||
if type(profile) is not dict:
|
||||
data['profile'] = json.loads(profile)
|
||||
else:
|
||||
data['profile'] = profile
|
||||
data['verifications'] = fetch_proofs(data['profile'], username,
|
||||
refresh=refresh)
|
||||
|
||||
data['zone_file'] = zone_file
|
||||
if type(profile) is not dict:
|
||||
data['profile'] = json.loads(profile)
|
||||
data['verifications'] = fetch_proofs(data['profile'], username,
|
||||
refresh=refresh)
|
||||
|
||||
return data
|
||||
|
||||
NAME_PATTERN = re.compile(OP_NAME_PATTERN)
|
||||
NS_PATTERN = re.compile(OP_NAMESPACE_PATTERN)
|
||||
def is_valid_fqa(fqa):
|
||||
try:
|
||||
username, ns = fqa.split(".")
|
||||
except:
|
||||
return False
|
||||
return ((NAME_PATTERN.match(username) is not None) and
|
||||
(NS_PATTERN.match(ns) is not None))
|
||||
|
||||
def get_profile(username, refresh=False, namespace=DEFAULT_NAMESPACE):
|
||||
def get_profile(fqa, refresh=False):
|
||||
""" Given a fully-qualified username (username.namespace)
|
||||
get the data associated with that fqu.
|
||||
Return cached entries, if possible.
|
||||
@@ -298,22 +180,23 @@ def get_profile(username, refresh=False, namespace=DEFAULT_NAMESPACE):
|
||||
global MEMCACHED_ENABLED
|
||||
global mc
|
||||
|
||||
username = username.lower()
|
||||
fqa = fqa.lower()
|
||||
if not is_valid_fqa(fqa):
|
||||
return {'error' : 'Malformed name {}'.format(fqa)}
|
||||
|
||||
if MEMCACHED_ENABLED and not refresh:
|
||||
log.debug("Memcache get DHT: %s" % username)
|
||||
dht_cache_reply = mc.get("dht_" + str(username))
|
||||
log.debug("Memcache get DHT: %s" % fqa)
|
||||
dht_cache_reply = mc.get("dht_" + str(fqa))
|
||||
else:
|
||||
log.debug("Memcache disabled: %s" % username)
|
||||
dht_cache_reply = None
|
||||
|
||||
if dht_cache_reply is None:
|
||||
try:
|
||||
profile, zonefile = blockstack_client.profile.get_profile(
|
||||
"{}.{}".format(username, namespace),
|
||||
use_legacy = True)
|
||||
fqa, use_legacy = True)
|
||||
except:
|
||||
abort(500, "Connection to blockstack-server %s:%s timed out" % (BLOCKSTACKD_IP, BLOCKSTACKD_PORT))
|
||||
abort(500, "Connection to blockstack-server %s:%s timed out" %
|
||||
(BLOCKSTACKD_IP, BLOCKSTACKD_PORT))
|
||||
|
||||
if profile is None or 'error' in zonefile:
|
||||
log.error("{}".format(zonefile))
|
||||
@@ -322,13 +205,13 @@ def get_profile(username, refresh=False, namespace=DEFAULT_NAMESPACE):
|
||||
prof_data = {'response' : profile}
|
||||
|
||||
if MEMCACHED_ENABLED or refresh:
|
||||
log.debug("Memcache set DHT: %s" % username)
|
||||
mc.set("dht_" + str(username), json.dumps(data),
|
||||
log.debug("Memcache set DHT: %s" % fqa)
|
||||
mc.set("dht_" + str(fqa), json.dumps(data),
|
||||
int(time() + MEMCACHED_TIMEOUT))
|
||||
else:
|
||||
prof_data = json.loads(dht_cache_reply)
|
||||
|
||||
data = format_profile(prof_data['response'], username, zonefile)
|
||||
data = format_profile(prof_data['response'], fqa, zonefile)
|
||||
|
||||
return data
|
||||
|
||||
@@ -337,15 +220,10 @@ def get_all_users():
|
||||
""" Return all users in the .id namespace
|
||||
"""
|
||||
|
||||
try:
|
||||
fout = open(NAMES_FILE, 'r')
|
||||
data = fout.read()
|
||||
data = json.loads(data)
|
||||
fout.close()
|
||||
except:
|
||||
data = {}
|
||||
|
||||
return data
|
||||
# aaron: hardcode a non-response for the time being --
|
||||
# the previous code was trying to load a non-existent file
|
||||
# anyways.
|
||||
return {}
|
||||
|
||||
# aaron note: do we need to support multiple users in a query?
|
||||
# this seems like a potential avenue for abuse.
|
||||
@@ -356,7 +234,6 @@ def get_all_users():
|
||||
def get_users(usernames):
|
||||
""" Fetch data from username in .id namespace
|
||||
"""
|
||||
|
||||
reply = {}
|
||||
refresh = False
|
||||
|
||||
@@ -367,39 +244,28 @@ def get_users(usernames):
|
||||
|
||||
if usernames is None:
|
||||
reply['error'] = "No usernames given"
|
||||
return jsonify(reply)
|
||||
return jsonify(reply), 404
|
||||
|
||||
if ',' not in usernames:
|
||||
|
||||
username = usernames
|
||||
|
||||
info = get_profile(username, refresh=refresh)
|
||||
|
||||
if 'error' in info:
|
||||
reply[username] = info
|
||||
return jsonify(reply), 502
|
||||
else:
|
||||
reply[username] = info
|
||||
|
||||
return jsonify(reply), 200
|
||||
|
||||
try:
|
||||
usernames = usernames.rsplit(',')
|
||||
except:
|
||||
reply['error'] = "Invalid input format"
|
||||
return jsonify(reply)
|
||||
usernames = [usernames]
|
||||
else:
|
||||
try:
|
||||
usernames = usernames.rsplit(',')
|
||||
except:
|
||||
reply['error'] = "Invalid input format"
|
||||
return jsonify(reply), 401
|
||||
|
||||
for username in usernames:
|
||||
if "." not in username:
|
||||
username = "{}.{}".format(username, 'id')
|
||||
profile = get_profile(username, refresh=refresh)
|
||||
|
||||
try:
|
||||
profile = get_profile(username, refresh=refresh)
|
||||
|
||||
if 'error' in profile:
|
||||
pass
|
||||
else:
|
||||
if 'error' in profile:
|
||||
if len(usernames) == 1:
|
||||
reply[username] = profile
|
||||
except:
|
||||
pass
|
||||
return jsonify(reply), 502
|
||||
else:
|
||||
reply[username] = profile
|
||||
|
||||
return jsonify(reply), 200
|
||||
|
||||
|
||||
Reference in New Issue
Block a user