Exemple #1
0
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 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
Exemple #3
0
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, 'public_key': ...} 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

    Returns {'error': ...} on error
    """

    proxy = get_default_proxy() if proxy is None else proxy
    user_profile_pubkey = None

    res = subdomains.is_address_subdomain(str(name))
    if res:
        subdomain, domain = res[1]
        try:
            return subdomains.resolve_subdomain(subdomain, domain)
        except subdomains.SubdomainNotFound as e:
            log.exception(e)
            return {
                'error': "Failed to find name {}.{}".format(subdomain, domain)
            }

    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)

    else:
        # get user's data public key
        data_address, owner_address = None, None

        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 = None
        user_profile_pubkey = None

        try:
            user_profile_res = 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,
                return_public_key=True)

            user_profile = user_profile_res['data']
            user_profile_pubkey = user_profile_res['public_key']

        except Exception as e:
            log.exception(e)
            return {'error': 'Failure in parsing and fetching 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,
        'public_key': user_profile_pubkey
    }

    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
Exemple #4
0
def get_and_migrate_profile(name,
                            zonefile_storage_drivers=None,
                            profile_storage_drivers=None,
                            proxy=None,
                            create_if_absent=False,
                            wallet_keys=None,
                            include_name_record=False):
    """
    Get a name's profile and zonefile, optionally creating a new one along the way.  Migrate the profile to a new zonefile,
    if the profile is in legacy format.

    Only pass 'create_if_absent=True' for names we own

    If @include_name_record is set, then the resulting zonefile will have a key called 'name_record' that includes the name record.

    @wallet_keys, if given, only needs the data public key set.

    Return ({'profile': user_profile}, {'zonefile': user_zonefile}, migrated:bool) on success
    Return ({'error': ...}, None, False) on error
    """

    if proxy is None:
        proxy = get_default_proxy()

    created_new_zonefile = False
    created_new_profile = False

    name_record = None
    user_zonefile = get_name_zonefile(name,
                                      storage_drivers=zonefile_storage_drivers,
                                      proxy=proxy,
                                      wallet_keys=wallet_keys,
                                      include_name_record=True)
    if user_zonefile is None or 'error' in user_zonefile:
        if not create_if_absent:
            return ({'error': 'No such zonefile'}, None, False)

        # creating. we'd better have a data public key
        log.debug("Creating new profile and zonefile for name '%s'" % name)

        data_pubkey = load_data_pubkey_for_new_zonefile(
            wallet_keys=wallet_keys, config_path=proxy.conf['path'])
        if data_pubkey is None:
            log.warn(
                "No data keypair set; will fall back to owner private key for data signing"
            )

        user_profile = user_db.make_empty_user_profile()
        user_zonefile = user_db.make_empty_user_zonefile(name, data_pubkey)

        # look up name too
        name_record = proxy.get_name_blockchain_record(name)
        if name_record is None:
            return ({'error': 'No such name'}, None, False)

        if 'error' in name_record:
            return ({
                'error':
                'Failed to look up name: %s' % name_record['error']
            }, None, False)

        created_new_zonefile = True
        created_new_profile = True

    else:
        name_record = user_zonefile['name_record']
        del user_zonefile['name_record']
        user_zonefile = user_zonefile['zonefile']

    if blockstack_profiles.is_profile_in_legacy_format(
            user_zonefile) or not user_db.is_user_zonefile(user_zonefile):

        log.debug("Migrating legacy profile to modern zonefile for name '%s'" %
                  name)

        data_pubkey = load_data_pubkey_for_new_zonefile(
            wallet_keys=wallet_keys, config_path=proxy.conf['path'])
        if data_pubkey is None:
            log.warn(
                "No data keypair set; will fall back to owner private key for data signing"
            )

        user_profile = {}
        if blockstack_profiles.is_profile_in_legacy_format(user_zonefile):
            # traditional profile
            user_profile = blockstack_profiles.get_person_from_legacy_format(
                user_zonefile)
        else:
            # custom profile
            user_profile = copy.deepcopy(user_zonefile)

        user_zonefile = user_db.make_empty_user_zonefile(name, data_pubkey)

        created_new_zonefile = True
        created_new_profile = True

    else:
        if not created_new_profile:
            user_profile, error_msg = get_name_profile(
                name,
                zonefile_storage_drivers=zonefile_storage_drivers,
                profile_storage_drivers=profile_storage_drivers,
                proxy=proxy,
                user_zonefile=user_zonefile,
                name_record=name_record)
            if user_profile is None:
                return (error_msg, None, False)

        elif create_if_absent:
            log.debug(
                "Creating new profile for existing zonefile for name '%s'" %
                name)
            user_profile = user_db.make_empty_user_profile()
            created_new_profile = True

        else:
            raise Exception("Should be unreachable")

    ret_user_profile = {"profile": user_profile}

    ret_user_zonefile = {"zonefile": user_zonefile}

    if include_name_record:
        # put it back
        ret_user_zonefile['name_record'] = name_record

    return (ret_user_profile, ret_user_zonefile, created_new_zonefile)
Exemple #5
0
def get_name_profile(name,
                     zonefile_storage_drivers=None,
                     profile_storage_drivers=None,
                     create_if_absent=False,
                     proxy=None,
                     user_zonefile=None,
                     name_record=None,
                     include_name_record=False,
                     include_raw_zonefile=False,
                     use_zonefile_urls=True,
                     decode_profile=True):
    """
    Given the name of the user, look up the user's record hash,
    and then get the record itself from storage.

    If the user's zonefile is really a legacy profile, then 
    the profile 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.

    Returns (profile, zonefile) on success.  If include_name_record is True, then zonefile['name_record'] will be defined and will contain the user's blockchain information
    Returns (None, {'error': ...}) on failure
    """

    if proxy is None:
        proxy = get_default_proxy()

    raw_zonefile = None

    if user_zonefile is None:
        user_zonefile = get_name_zonefile(
            name,
            create_if_absent=create_if_absent,
            proxy=proxy,
            name_record=name_record,
            include_name_record=True,
            storage_drivers=zonefile_storage_drivers,
            include_raw_zonefile=include_raw_zonefile)
        if user_zonefile is None:
            return (None, {'error': 'No user zonefile'})

        if 'error' in user_zonefile:
            return (None, user_zonefile)

        name_record = user_zonefile['name_record']
        del user_zonefile['name_record']

        raw_zonefile = None
        if include_raw_zonefile:
            raw_zonefile = user_zonefile['raw_zonefile']
            del user_zonefile['raw_zonefile']

        user_zonefile = user_zonefile['zonefile']

    # is this really a legacy profile?
    if blockstack_profiles.is_profile_in_legacy_format(user_zonefile):
        # 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):
        # not a legacy profile, but a custom profile
        log.debug("Using custom legacy profile")
        user_profile = copy.deepcopy(user_zonefile)

    else:
        # get user's data public key
        user_address = None
        old_address = None

        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)
                user_address = virtualchain.BitcoinPublicKey(
                    user_data_pubkey).address()

        except ValueError:
            # user decided to put multiple keys under the same name into the zonefile.
            # so don't use them.
            user_data_pubkey = None

        # convert to address
        if name_record is None:
            name_record = proxy.get_name_blockchain_record(name)
            if name_record is None or 'error' in name_record:
                log.error("Failed to look up name record for '%s'" % name)
                return (None, {'error': 'Failed to look up name record'})

        old_address = name_record['address']
        if user_address is None:
            # cut to the chase
            user_address = old_address

        user_profile = load_name_profile(
            name,
            user_zonefile,
            user_address,
            old_address,
            use_zonefile_urls=use_zonefile_urls,
            storage_drivers=profile_storage_drivers,
            decode=decode_profile)

        if user_profile is not None:
            if decode_profile:
                assert isinstance(user_profile, dict)
            else:
                assert type(user_profile) in [str, unicode]

        else:
            log.debug("WARN: no user profile for %s" % name)
            if create_if_absent:
                user_profile = user_db.make_empty_user_profile()
            else:
                return (None, {'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.
    if include_name_record:
        if name_record is None:
            name_record = proxy.get_name_blockchain_record(name)
            if name_record is None or 'error' in name_record:
                log.error("Failed to look up name record for '%s'" % name)
                return (None, {'error': 'Failed to look up name record'})

        user_zonefile['name_record'] = name_record

    if include_raw_zonefile:
        if raw_zonefile is not None:
            user_zonefile['raw_zonefile'] = raw_zonefile

    return (user_profile, user_zonefile)
Exemple #6
0
        return None

    try:
        data_json = json.loads(data)
    except Exception, 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 %s, got %s" %
                  (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 load_name_profile(name,
                      user_zonefile,
                      data_address,
                      owner_address,
                      use_zonefile_urls=True,
                      storage_drivers=None,
                      decode=True):
    """
    Fetch and load a user profile, given the user zonefile.
    Try to verify using the public key in the zonefile (if one
    is present), and fall back to the user-address if need be
    (it should be the hash of the profile JWT's public key).
Exemple #7
0
    def test_profile_format_migration(self):
        legacy_profile_1 = {
            "data_record": {
                "avatar": {
                    "url": "https://s3.amazonaws.com/kd4/judecn"
                },
                "bio": "PhD student",
                "bitcoin": {
                    "address": "17zf596xPvV8Z8ThbWHZHYQZEURSwebsKE"
                },
                "cover": {
                    "url": "https://s3.amazonaws.com/97p/gQZ.jpg"
                },
                "facebook": {
                    "proof": {
                        "url":
                        "https://facebook.com/sunspider/posts/674912239245011"
                    },
                    "username": "******"
                },
                "github": {
                    "proof": {
                        "url":
                        "https://gist.github.com/jcnelson/70c02f80f8d4b0b8fc15"
                    },
                    "username": "******"
                },
                "location": {
                    "formatted": "Princeton University"
                },
                "name": {
                    "formatted": "Jude Nelson"
                },
                "twitter": {
                    "proof": {
                        "url":
                        "https://twitter.com/judecnelson/status/507374756291555328"
                    },
                    "username": "******"
                },
                "v": "0.2",
                "website": "http://www.cs.princeton.edu/~jcnelson"
            }
        }

        legacy_profile_2 = {
            "data_record": {
                "avatar": {
                    "url": "https://s3.amazonaws.com/kd4/muneeb"
                },
                "bio":
                "Co-founder of Onename (YC S14), final-year PhD candidate at Princeton. Interested in distributed systems and blockchains.",
                "bitcoin": {
                    "address": "1LNLCwtigWAvLkNakUK4jnmmvdVvmULeES"
                },
                "cover": {
                    "url": "https://s3.amazonaws.com/dx3/muneeb"
                },
                "facebook": {
                    "proof": {
                        "url":
                        "https://facebook.com/muneeb.ali/posts/10152524743274123"
                    },
                    "username": "******"
                },
                "github": {
                    "proof": {
                        "url": "https://gist.github.com/muneeb-ali/9838362"
                    },
                    "username": "******"
                },
                "graph": {
                    "followee_count": 4,
                    "url": "https://s3.amazonaws.com/grph/muneeb"
                },
                "location": {
                    "formatted": "New York, NY"
                },
                "name": {
                    "formatted": "Muneeb Ali"
                },
                "pgp": {
                    "fingerprint": "9862A3FB338BE9EB6C6A5E05639C89272AFEC540",
                    "url": "http://muneebali.com/static/files/key.asc"
                },
                "twitter": {
                    "proof": {
                        "url":
                        "https://twitter.com/muneeb/status/483765788478689280"
                    },
                    "username": "******"
                },
                "v": "0.2",
                "website": "http://muneebali.com"
            }
        }
        zone_file_1 = get_person_from_legacy_format(legacy_profile_1)
        zone_file_2 = get_person_from_legacy_format(legacy_profile_2)
        print json.dumps(zone_file_1, indent=2, sort_keys=True)
        print json.dumps(zone_file_2, indent=2, sort_keys=True)
        self.assertTrue(isinstance(zone_file_1, dict))
        self.assertTrue(isinstance(zone_file_2, dict))
 def test_profile_format_migration(self):
     zone_file = get_person_from_legacy_format(reference_profiles["jude"])
     # print json.dumps(zone_file, indent=2, sort_keys=True)
     self.assertTrue(isinstance(zone_file, dict))
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

    Returns {'error': ...} on error
    """

    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)

    else:
        # get user's data public key
        data_address, owner_address = None, None

        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
    }

    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 test_profile_format_migration(self):
     zone_file = get_person_from_legacy_format(reference_profiles["jude"])
     # print json.dumps(zone_file, indent=2, sort_keys=True)
     self.assertTrue(isinstance(zone_file, dict))
Exemple #11
0
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 (profile, zonefile) on success.  If include_name_record is True, then zonefile['name_record'] will be defined and will contain the user's blockchain information
    Returns (None, {'error': ...}) on failure
    """

    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 None, 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 (None, {'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 (None, {'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)

    else:
        # get user's data public key
        data_address, owner_address = None, None

        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 = virtualchain.BitcoinPublicKey(
                    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 (None, {'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 None, {'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,
            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.debug('WARN: no user profile for {}'.format(name))
            else:
                log.debug('WARN: failed to load profile for {}: {}'.format(
                    name, user_profile['error']))

            return None, {'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.
    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 None, {'error': 'Failed to look up name record'}

        user_zonefile['name_record'] = name_record

    if include_raw_zonefile:
        if raw_zonefile is not None:
            user_zonefile['raw_zonefile'] = raw_zonefile

    return user_profile, user_zonefile
Exemple #12
0
def get_and_migrate_profile( name, zonefile_storage_drivers=None, profile_storage_drivers=None, proxy=None, create_if_absent=False, wallet_keys=None, include_name_record=False ):
    """
    Get a name's profile and zonefile, optionally creating a new one along the way.  Migrate the profile to a new zonefile,
    if the profile is in legacy format.

    Only pass 'create_if_absent=True' for names we own

    If @include_name_record is set, then the resulting zonefile will have a key called 'name_record' that includes the name record.

    Return (user_profile, user_zonefile, migrated:bool) on success
    Return ({'error': ...}, None, False) on error
    """

    if proxy is None:
        proxy = get_default_proxy()

    created_new_zonefile = False
    created_new_profile = False

    name_record = None
    user_zonefile = get_name_zonefile( name, storage_drivers=zonefile_storage_drivers, proxy=proxy, wallet_keys=wallet_keys, include_name_record=True )
    if user_zonefile is None or 'error' in user_zonefile: 
        if not create_if_absent:
            return ({'error': 'No such zonefile'}, None, False)

        # creating. we'd better have a data public key
        log.debug("Creating new profile and zonefile for name '%s'" % name)
            
        data_pubkey = load_data_pubkey_for_new_zonefile( wallet_keys=wallet_keys, config_path=proxy.conf['path'] )
        if data_pubkey is None:
            log.warn("No data keypair set; will fall back to owner private key for data signing")

        user_profile = user_db.make_empty_user_profile()
        user_zonefile = user_db.make_empty_user_zonefile( name, data_pubkey )

        # look up name too 
        name_record = proxy.get_name_blockchain_record(name)
        if name_record is None:
            return ({'error': 'No such name'}, None, False)

        if 'error' in name_record:
            return ({'error': 'Failed to look up name: %s' % name_record['error']}, None, False)

        created_new_zonefile = True
        created_new_profile = True
    
    else:
        name_record = user_zonefile['name_record']
        del user_zonefile['name_record']

    if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ) or not user_db.is_user_zonefile( user_zonefile ):

        log.debug("Migrating legacy profile to modern zonefile for name '%s'" % name)
        
        data_pubkey = load_data_pubkey_for_new_zonefile( wallet_keys=wallet_keys, config_path=proxy.conf['path'] )
        if data_pubkey is None:
            log.warn("No data keypair set; will fall back to owner private key for data signing")

        user_profile = {}
        if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ):
            # traditional profile
            user_profile = blockstack_profiles.get_person_from_legacy_format( user_zonefile )
        else:
            # custom profile 
            user_profile = copy.deepcopy( user_zonefile )

        user_zonefile = user_db.make_empty_user_zonefile( name, data_pubkey )

        created_new_zonefile = True
        created_new_profile = True

    else:
        if not created_new_profile:
            user_profile, error_msg = get_name_profile( name, zonefile_storage_drivers=zonefile_storage_drivers, profile_storage_drivers=profile_storage_drivers,
                                                        proxy=proxy, wallet_keys=wallet_keys, user_zonefile=user_zonefile, name_record=name_record )
            if user_profile is None:
                return (error_msg, None, False)

        elif create_if_absent:
            log.debug("Creating new profile for existing zonefile for name '%s'" % name)
            user_profile = user_db.make_empty_user_profile()
            created_new_profile = True

        else:
            raise Exception("Should be unreachable")


    if include_name_record:
        # put it back
        user_zonefile['name_record'] = name_record 

    return (user_profile, user_zonefile, created_new_zonefile)
Exemple #13
0
def get_name_profile(name, zonefile_storage_drivers=None,
                           profile_storage_drivers=None,
                           create_if_absent=False,
                           proxy=None,
                           wallet_keys=None,
                           user_zonefile=None,
                           name_record=None,
                           include_name_record=False,
                           use_zonefile_urls=True,
                           decode_profile=True ):

    """
    Given the name of the user, look up the user's record hash,
    and then get the record itself from storage.

    If the user's zonefile is really a legacy profile, then 
    the profile 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.

    Returns (profile, zonefile) on success.  If include_name_record is True, then zonefile['name_record'] will be defined and will contain the user's blockchain information
    Returns (None, {'error': ...}) on failure
    """

    if proxy is None:
        proxy = get_default_proxy()
 
    if user_zonefile is None:
        user_zonefile = get_name_zonefile( name, create_if_absent=create_if_absent, proxy=proxy, wallet_keys=wallet_keys, name_record=name_record, include_name_record=True, storage_drivers=zonefile_storage_drivers )
        if user_zonefile is None:
            return (None, {'error': 'No user zonefile'})

        if 'error' in user_zonefile:
            return (None, user_zonefile)

        name_record = user_zonefile['name_record']
        del user_zonefile['name_record']

    # is this really a legacy profile?
    if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ):
        # 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 ):
        # not a legacy profile, but a custom profile
        log.debug("Using custom legacy profile")
        user_profile = copy.deepcopy(user_zonefile)

    else:
        # get user's data public key
        user_address = None
        old_address = None

        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)
                user_address = pybitcoin.BitcoinPublicKey(user_data_pubkey).address()

        except ValueError:
            # user decided to put multiple keys under the same name into the zonefile.
            # so don't use them.
            user_data_pubkey = None 

        # convert to address
        if name_record is None:
            name_record = proxy.get_name_blockchain_record( name )
            if name_record is None or 'error' in name_record:
                log.error("Failed to look up name record for '%s'" % name)
                return (None, {'error': 'Failed to look up name record'})

        old_address = name_record['address']
        if user_address is None:
            # cut to the chase
            user_address = old_address

        user_profile = load_name_profile( name, user_zonefile, user_address, old_address, use_zonefile_urls=use_zonefile_urls, storage_drivers=profile_storage_drivers, decode=decode_profile )
        if user_profile is None or (type(user_profile) not in [str, unicode] and 'error' in user_profile):

            if user_profile is None:
                log.debug("WARN: no user profile for %s" % name)
            else:
                log.debug("WARN: failed to load profile for %s: %s" % (name, user_profile['error']))

            if create_if_absent:
                user_profile = user_db.make_empty_user_profile()
            else:
                return (None, {'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.
    if include_name_record:
        if name_record is None:
            name_record = proxy.get_name_blockchain_record( name )
            if name_record is None or 'error' in name_record:
                log.error("Failed to look up name record for '%s'" % name)
                return (None, {'error': 'Failed to look up name record'})

        user_zonefile['name_record'] = name_record

    return (user_profile, user_zonefile)
Exemple #14
0
        log.error("Failed to fetch http://%s/%s: %s" % (storage_host, storage_path, e))
        return None 

    try:
        data_json = json.loads(data)
    except Exception, 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 %s, got %s" % (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 load_name_profile(name, user_zonefile, data_address, owner_address, use_zonefile_urls=True, storage_drivers=None, decode=True):
    """
    Fetch and load a user profile, given the user zonefile.
    Try to verify using the public key in the zonefile (if one
    is present), and fall back to the user-address if need be
    (it should be the hash of the profile JWT's public key).

    Return the user profile on success (either as a dict, or as a string if decode=False)
    Return None on error
    """
    # get user's data public key
    try: