WIP: file-sharing demo in Python with the RESTful API

This commit is contained in:
Jude Nelson
2017-03-01 19:34:33 -05:00
parent 1586cef169
commit 34a5d9667b
6 changed files with 435 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
Demo: Blockstack-files
======================
CLI program for loading and storing files with Blockstack's RESTful API.

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
This code is public domain.
Used as a demo for Bockstack.
"""
import os
import sys
if sys.version_info.major != 2:
raise Exception("Python 3 is not supported")
if sys.version_info.minor < 7:
raise Exception("Python 2.7 or greater is required")
# Hack around absolute paths
current_dir = os.path.abspath(os.path.dirname(__file__))
parent_dir = os.path.abspath(current_dir + "/../")
sys.path.insert(0, parent_dir)
from blockstack_files.main import main
if __name__ == '__main__':
main(sys.argv)

View File

@@ -0,0 +1,368 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
This code is public domain.
Used as a demo for Bockstack.
"""
import requests
import urllib
import os
import sys
# need to pip install these
import blockstack_client as bsk
import jsontokens
import keylib
# application domain name (identifies the specific data store)
APPLICATION_NAME = 'files.blockstack.org'
# list of API families
APPLICATION_METHODS = [
'store_read',
'store_write',
'store_admin',
]
# ECDSA private key (different each time)
APPLICATION_KEY = keylib.ECPrivateKey().to_hex()
def bsk_get_url( path, port=6270 ):
"""
Make a URL to the local blockstack node
"""
return 'http://localhost:{}{}'.format(port, path)
def bsk_auth_headers( session ):
"""
Make authorization headers from a session token
"""
return {'Authorization': 'bearer {}'.format(session)}
def bsk_get_session( api_password, port=6270 ):
"""
Connect to the local blockstack node.
Get back a session.
Return the session (a JWT string) on success
Raise on error
"""
# will call `GET http://localhost:{port}/v1/auth?authRequest={auth JWT}`
# will get back a session JWT
# request permission to access the API
auth_request = {
'app_domain': APPLICATION_NAME,
'methods': APPLICATION_METHODS,
}
# authentication: basic {password}
headers = {
'Authorization': 'basic {}'.format(api_password)
}
# make the authentication token
signer = jsontokens.TokenSigner()
auth_token = signer.sign(auth_request, APPLICATION_KEY)
# ask for a session token
url = bsk_get_url('/v1/auth?authRequest={}'.format(auth_token), port=port)
req = requests.get(url, headers=headers )
if req.status_code == 200:
# good to go!
# expect {'session': ses token} JSON response
payload = req.json()
session = payload['token']
return session
else:
# whoops!
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_make_datastore( session, drivers=None, port=6270 ):
"""
Make our data store. The data store will be specific
to this application's name (APPLICATION_NAME)
Return the datastore ID on success (even if it already exists)
Raise on error
"""
# create a datastore with `POST http://localhost:{port}/v1/stores?drivers={drivers}`
# get back {'status': True, 'app_user_id': datastore ID}
# does this data store already exist?
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}'.format(APPLICATION_NAME), port=port)
req = requests.get(url, headers=auth_headers)
if req.status_code == 200:
# the data store already exists. Give back it's ID
datastore_info = req.json()
datastore_id = datastore_info['datastore_id']
return datastore_id
elif req.status_code != 404:
# some other error
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
# doesn't exist yet. Go create it.
url = bsk_get_url('/v1/stores', port=port)
if drivers is not None:
# include requested drivers
url += '?drivers={}'.format( urllib.quote(','.join(drivers)) )
req = requests.post(url, headers=auth_headers)
if req.status_code == 200:
# succeeded!
# get back the ID
datastore_res = req.json()
datastore_id = datastore_res['datastore_id']
return datastore_id
else:
# something broke!
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_delete_datastore( session, port=6270 ):
"""
Delete our data store.
Return True on success
Raise on error
"""
# delete datastore with `DELETE http://localhost:{port}/v1/stores`
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores', port=port)
req = requests.delete(url, headers=auth_headers)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_put_file( session, datastore_id, local_path, bsk_path, port=6270 ):
"""
Put the file data pointed to by {local_path} into
{bsk_path}
Return True on success
Raise on error
"""
# put the file with `POST http://localhost:{port}/v1/stores/{store ID}/files?path={bsk_path}`
# get back {'status': True}
# get data from disk
with open(local_path, 'r') as f:
data = f.read()
# issue the request to store
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/files?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.post(url, headers=auth_headers, data=data)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_get_file( session, datastore_id, bsk_path, port=6270, file=sys.stdout ):
"""
Get the file data pointed to by {bsk_path} from the data store.
Write it to stdout by default (override with file=)
Return True on success
Raise on error
"""
# get the file with `GET http://localhost:{port}/v1/stores/{store ID}/files?path={bsk_path}`
# get back raw data (application/octet-stream)
# issue the request to fetch
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/files?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.get(url, headers=auth_headers)
if req.status_code == 200:
# success!
print >> file, req.content
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_delete_file( session, datastore_id, bsk_path, port=6270 ):
"""
Delete the file data pointed to by {bsk_path} from the data store.
Return True on success
Raise on error
"""
# delete with `DELETE http://localhost:{port}/v1/stores/{store ID}/files?path={bsk_path)`
# get back {'status': True}
# issue the request
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/files?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.delete(url, headers=auth_headers)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_mkdir( session, datastore_id, bsk_path, port=6270 ):
"""
Make a directory at {bsk_path} in the given data store
Return True on success
Raise on error
"""
# make the directory with `POST http://localhost:{port}/v1/stores/{store ID}/directories?path={bsk_path}`
# get back {'status': True} (http 200)
# issue the request to fetch
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/directories?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.post(url, headers=auth_headers)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_listdir( session, datastore_id, bsk_path, port=6270, file=sys.stdout ):
"""
List a directory at {bsk_path} in the given data store.
Write the listing to stdout.
Return True on success
Raise on error
"""
# list the directory with `GET http://localhost:{port}/v1/stores/{store ID}/directories?path={bsk_path}`
# get back the structured inode of the directory.
# iterate through the inode's children and print their names and types (i.e. append '/' to directories)
# issue the request to fetch
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/directories?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.get(url, headers=auth_headers)
if req.status_code == 200:
# success!
dir_listing = req.json()
# extract names and types
names = []
for name in dir_listing.keys():
dirent = dir_listing[name]
if dirent['type'] == 2: # this is a file
name += '/'
names.append(name)
names.sort()
print >> file, '\n'.join(names)
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_rmdir( session, datastore_id, bsk_path, port=6270 ):
"""
Remove a directory at {bsk_path} in the given data store.
Return True on success
Raise on error
"""
# delete the directory with `DELETE http://localhost:{port}/v1/stores/{store ID}/directories?path={bsk_path}`
# issue the request
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/directories?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.delete(url, headers=auth_headers)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
if __name__ == '__main__':
# unit tests!
import StringIO
api_password = sys.argv[1]
ses = bsk_get_session(api_password)
assert ses, "Failed to authentcate"
datastore_id = bsk_make_datastore(ses, ['disk'])
assert datastore_id, "Failed to make datastore"
# load something
with open("/tmp/.footest", 'w') as f:
f.write("hello world\x00\x01\x02\x03\x04")
res = bsk_put_file(ses, datastore_id, "/tmp/.footest", "/foo")
assert res, "Failed to put file"
# get it back
sb = StringIO.StringIO()
res = bsk_get_file(ses, datastore_id, "/foo", file=sb)
assert res, "Failed to get file"
assert sb.getvalue() == "hello world\x00\x01\x02\x03\x04\n", "Got wrong data ({})".format(sb.getvalue())
# make a directory
res = bsk_mkdir(ses, datastore_id, "/bar")
assert res, "Failed to mkdir"
# list directory
sb = StringIO.StringIO()
res = bsk_listdir(ses, datastore_id, "/", file=sb)
assert res, "Failed to listdir"
assert sb.getvalue() == "bar/\nfoo\n", "Got wrong dir ({})".format(sb.getvalue())
# delete file
res = bsk_delete_file(ses, datastore_id, '/foo')
assert res
# delete directory
res = bsk_rmdir(ses, datastore_id, '/bar')
assert res
# delete datastore
res = bsk_delete_datastore(ses)
assert res

View File

@@ -0,0 +1 @@
__version__='0.0.1'

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
# to set __version__
exec(open('blockstack_files/version.py').read())
setup(
name='blockstack-files',
version=__version__,
url='https://github.com/blockstack/blockstack-core',
license='GPLv3',
author='Blockstack.org',
author_email='support@blockstack.org',
description='Blockstack encrypted file sharing demo',
keywords='blockchain git crypography name key value store data',
packages=find_packages(),
download_url='https://github.com/blockstack/blockstack-core/archive/master.zip',
zip_safe=False,
include_package_data=True,
scripts=['bin/blockstack-files'],
install_requires=[
'blockstack>=0.14.1',
'pyelliptic>=1.5.7',
],
classifiers=[
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Internet',
'Topic :: Security :: Cryptography',
'Topic :: Software Development :: Libraries :: Python Modules',
],
)