Skip to content

Commit

Permalink
Merge pull request #267 from superphy/bearer
Browse files Browse the repository at this point in the history
MERGE: 6.1.0 bearer accounts
  • Loading branch information
kevinkle authored Jan 25, 2018
2 parents 285837e + e02131e commit e155195
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 10 deletions.
2 changes: 2 additions & 0 deletions app/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from routes.ra_pan import bp_ra_pan
from routes.alive import bp_alive
from routes.ra_restricted import bp_ra_restricted
from routes.ra_accounts import bp_ra_accounts

# Auth0
# Error handler
Expand Down Expand Up @@ -68,5 +69,6 @@ def handle_auth_error(ex):
app.register_blueprint(bp_ra_pan)
app.register_blueprint(bp_alive)
app.register_blueprint(bp_ra_restricted)
app.register_blueprint(bp_ra_accounts)

return app
31 changes: 31 additions & 0 deletions app/middleware/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@

from config import AUTH0_DOMAIN, API_AUDIENCE, ALGORITHMS

from middleware.mongo import mongo_find

# Auth0
# Error handler
class AuthError(Exception):
def __init__(self, error, status_code):
self.error = error
self.status_code = status_code

# Format error response and append status code
def get_token_auth_header():
"""Obtains the access token from the Authorization Header
Expand Down Expand Up @@ -37,6 +46,28 @@ def get_token_auth_header():
token = parts[1]
return token

def validate_simple(token):
"""Checks that the given token exists.
"""
status = mongo_find(token, 'status')
return status == "active"

def requires_simple_auth(f):
"""A simple authentication check.
"""
@wraps(f)
def decorated(*args, **kwargs):
token = get_token_auth_header()
try:
assert validate_simple(token) == True
except Exception:
raise AuthError({"code": "invalid_header",
"description":
"Token doesn't exist"
" token."}, 400)
return f(*args, **kwargs)
return decorated

def requires_auth(f):
"""Determines if the access token is valid
"""
Expand Down
32 changes: 32 additions & 0 deletions app/middleware/bearer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import hashlib
import random
from datetime import datetime
# custom
from middleware.mongo import mongo_update

def generate_token():
"""
Generates a bearer token for use by the front-end.
Not actually secure, but suffices for our uses.
"""
now = datetime.now()
now = now.strftime("%Y-%m-%d-%H-%M-%S-%f")
salt = random.randint(100000,999999)
seed = "{0}{1}".format(now,salt)
token = hashlib.sha1(seed).hexdigest()
return token

def store(token):
"""
Stores the bearer token to MongoDB.
"""
# We should add a check for collision, but it's unlikely we'll see any.
# Add the account to mongo.
mongo_update(token, 'active', 'status')
# Create an empty jobs dictionary for the account.
mongo_update(token)

def bearer():
token = generate_token()
store(token)
return token
2 changes: 1 addition & 1 deletion app/middleware/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# Access the collection of accounts information from Spfy's DB
collection_accounts = db[MONGO_ACCOUNTSCOLLECTION]

def mongo_update(uid, json, key='store'):
def mongo_update(uid, json=[], key='store'):
'''By default, updates the 'store' document in the accounts collection.
'''
collection_accounts.update_one({'_id':uid},{'$set':{key:json}},upsert=True)
Expand Down
9 changes: 9 additions & 0 deletions app/routes/ra_accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from flask import Blueprint
from middleware.bearer import bearer

bp_ra_accounts = Blueprint('reactapp_accounts', __name__)

@bp_ra_accounts.route('/api/v0/accounts')
def create_account():
token = bearer()
return token
38 changes: 30 additions & 8 deletions app/routes/ra_restricted.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from flask import Blueprint, request, jsonify
from middleware.auth import requires_auth, requires_scope, get_sub_claim
from middleware.auth import requires_auth, requires_scope, get_token_auth_header, requires_simple_auth
from middleware.mongo import mongo_update, mongo_find

bp_ra_restricted = Blueprint('reactapp_restricted', __name__)
Expand All @@ -22,19 +22,41 @@ def secured_private_ping():
return "All good. You're authenticated and the access token has the appropriate scope"
return "You don't have access to this resource"

@bp_ra_restricted.route("/api/v0/secured/simple/ping")
@requires_simple_auth
def secured_simple_ping():
"""A valid access token and an appropriate scope are required to access this route
"""
return "All good. You only get this message if you're authenticated"

@bp_ra_restricted.route("/api/v0/secured/accounts/update", methods=['POST'])
@requires_auth
@requires_simple_auth
def update():
uid = get_sub_claim()
print('update() request:', request)
uid = get_token_auth_header()
print('update() request')
json = request.json
print(json)
# Get the store currently in the database.
previous_store = mongo_find(uid)
assert type(previous_store) is list
hashes = set()
# Create a set of the hashes.
for item in previous_store:
# Check if the item (eg. the job) has a hash.
if 'hash' in item:
hashes.add(item['hash'])
# Check the update.
for item in json:
if 'hash' in item and item['hash'] not in hashes:
previous_store.append(item)
# The previous_store should now be merged with the update.
print('update()', json)
mongo_update(uid,json)
return jsonify('true')
mongo_update(uid, previous_store)
return jsonify('success!')

@bp_ra_restricted.route("/api/v0/secured/accounts/find")
@requires_auth
@requires_simple_auth
def find():
uid = get_sub_claim()
uid = get_token_auth_header()
store = mongo_find(uid)
return jsonify(store)
13 changes: 13 additions & 0 deletions app/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ def test_api_internal_blazegraph():
cmd = '"curl {blazegraph}"'.format(blazegraph=blazegraph_url)
o = subprocess.check_output("""{exc} {cmd}""".format(exc=exc,cmd=cmd), shell=True, stderr=subprocess.STDOUT)
assert '</rdf:RDF>' in o

def test_simple_auth():
# Retrieve a bearer token from the api.
r = requests.get("""http://localhost:{port}/{api_root}accounts""".format(port=WEBSERVER_PORT,api_root=API_ROOT))
token = r.text
assert type(token) in (str, unicode)

# Check the bearer token allows access to a protected ping.
headers = {
'Authorization': 'Bearer ' + token
}
r = requests.get("""http://localhost:{port}/{api_root}secured/simple/ping""".format(port=WEBSERVER_PORT,api_root=API_ROOT), headers=headers)
assert r.text == "All good. You only get this message if you're authenticated"
2 changes: 1 addition & 1 deletion reactapp

0 comments on commit e155195

Please sign in to comment.