#!/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 . """ # use Blockstack Labs as a read-only reader for zonefile/profile data import os import sys import traceback import logging import json import requests import pybitcoin from ConfigParser import SafeConfigParser import blockstack_zones from common import get_logger, DEBUG log = get_logger("blockstack-storage-driver-blockstack-s3-readonly") if os.environ.get("BLOCKSTACK_TEST") is None: RESOLVER_URL = "https://onename.com" STORAGE_URL = "https://blockstack.s3.amazonaws.com" else: RESOLVER_URL = "http://localhost:8081" STORAGE_URL = "http://localhost:8081" def get_zonefile( fqu, zonefile_hash ): """ Try to get a zonefile, from onename.com Return the zonefile (serialized as a string) on success Return None on error """ fqu_json = fqu.split(".")[0] + ".json" url = "%s/%s" % (RESOLVER_URL, fqu_json) req = requests.get(url) if req.status_code != 200: log.debug("GET %s status code %s" % (url, req.status_code)) return None try: res = json.loads(req.content) except Exception, e: log.exception(e) log.debug("Failed to parse request to %s" % url) return None if 'zone_file' not in res.keys(): log.debug("No zonefile in request to %s" % url) return None zone_file_str = str(res['zone_file']) if pybitcoin.hex_hash160( zone_file_str ) != zonefile_hash: log.debug("Hash mismatch: expected %s, got %s" % (zonefile_hash, pybitcoin.hex_hash160(zone_file_str))) return None return zone_file_str def get_profile( fqu ): """ Try to get a profile, from S3 """ url = "%s/%s" % (STORAGE_URL, fqu) req = requests.get(url) if req.status_code != 200: log.debug("GET %s status code %s" % (url, req.status_code)) return None try: res = json.loads(req.content) except Exception, e: log.exception(e) log.debug("Failed to parse request to %s" % url) return None try: res = res[0] except: log.debug("JWT is not a list") return None for k in ['token', 'parentPublicKey']: if k not in res.keys(): log.debug("Missing required key '%s' in request to %s" % (k, url)) return None ret = {} ret['token'] = res['token'] ret['parentPublicKey'] = res['parentPublicKey'] return json.dumps([ret]) def storage_init(conf): # read config options from the config file, if given global STORAGE_URL, RESOLVER_URL config_path = conf['path'] if os.path.exists( config_path ): parser = SafeConfigParser() try: parser.read(config_path) except Exception, e: log.exception(e) return False if parser.has_section('blockstack-resolver-storage'): if parser.has_option('blockstack-resolver-storage', 'storage_url'): SERVER_NAME = parser.get('blockstack-resolver-storage', 'storage_url') if parser.has_option('blockstack-resolver-storage', 'resolver_url'): SERVER_PORT = int(parser.get('blockstack-resolver-storage', 'resolver_url')) # we can't proceed unless we have them. if STORAGE_URL is None or RESOLVER_URL is None: log.error("Config file '%s': section 'blockstack_resolver_storage' is missing 'resolver_url' and/or 'storage_url'") return False return True def handles_url( url ): if RESOLVER_URL in url or STORAGE_URL in url: return True else: return False def make_mutable_url( data_id ): # only deal with names if ':' in data_id: return None else: return "%s/%s" % (STORAGE_URL, data_id) def get_immutable_handler( key, **kw ): # only works for zonefiles, and we need the name if 'zonefile' in kw and not kw['zonefile']: return None fqu = kw.get('fqu', None) if fqu is None: return None return get_zonefile( fqu, key ) def get_mutable_handler( url, **kw ): # only works for profiles fqu = kw.get('fqu', None) if fqu is None: return None return get_profile( fqu ) def put_immutable_handler( key, data, txid, **kw ): # read only return False def put_mutable_handler( data_id, data_bin, **kw ): # read only return False def delete_immutable_handler( key, txid, sig_key_txid, **kw ): return True def delete_mutable_handler( data_id, signature, **kw ): return True