From 950832eb566966e0d235786c67655f0569910b73 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 19 Jan 2018 17:53:56 -0500 Subject: [PATCH 01/19] START: unauthenticated tokens to address refreshing --- app/middleware/token.py | 29 +++++++++++++++++++++++++++++ app/routes/ra_accounts.py | 9 +++++++++ 2 files changed, 38 insertions(+) create mode 100644 app/middleware/token.py create mode 100644 app/routes/ra_accounts.py diff --git a/app/middleware/token.py b/app/middleware/token.py new file mode 100644 index 00000000..aa77724c --- /dev/null +++ b/app/middleware/token.py @@ -0,0 +1,29 @@ +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. + """ + # Add the account to mongo. + mongo_update(token, 'active', 'status') + +def token(): + token = generate_token() + store(token) + return token diff --git a/app/routes/ra_accounts.py b/app/routes/ra_accounts.py new file mode 100644 index 00000000..6ba7372d --- /dev/null +++ b/app/routes/ra_accounts.py @@ -0,0 +1,9 @@ +from flask import Blueprint +from middleware.token import token + +bp_ra_accounts = Blueprint('reactapp_accounts', __name__) + +@bp_ra_db.route('/api/v0/accounts') +def create_account(): + token = token() + return token From ffc1370d890374134ef4c6c965df2dcfae518a2d Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 19 Jan 2018 17:57:50 -0500 Subject: [PATCH 02/19] ADD: route to factory --- app/factory.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/factory.py b/app/factory.py index 53e90203..27266695 100644 --- a/app/factory.py +++ b/app/factory.py @@ -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 @@ -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 From 398705b55b65cb903dc5faf64e56cc25db02ac07 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 19 Jan 2018 17:59:45 -0500 Subject: [PATCH 03/19] FIX: typo --- app/routes/ra_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/ra_accounts.py b/app/routes/ra_accounts.py index 6ba7372d..27a9dd18 100644 --- a/app/routes/ra_accounts.py +++ b/app/routes/ra_accounts.py @@ -3,7 +3,7 @@ bp_ra_accounts = Blueprint('reactapp_accounts', __name__) -@bp_ra_db.route('/api/v0/accounts') +@bp_ra_accounts.route('/api/v0/accounts') def create_account(): token = token() return token From 9becc255f5bfcc5d31618366331d6038db07175d Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 19 Jan 2018 18:03:07 -0500 Subject: [PATCH 04/19] FIX: right... names --- app/middleware/{token.py => bearer.py} | 3 ++- app/routes/ra_accounts.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) rename app/middleware/{token.py => bearer.py} (88%) diff --git a/app/middleware/token.py b/app/middleware/bearer.py similarity index 88% rename from app/middleware/token.py rename to app/middleware/bearer.py index aa77724c..0ce86065 100644 --- a/app/middleware/token.py +++ b/app/middleware/bearer.py @@ -20,10 +20,11 @@ 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') -def token(): +def bearer(): token = generate_token() store(token) return token diff --git a/app/routes/ra_accounts.py b/app/routes/ra_accounts.py index 27a9dd18..978a12ef 100644 --- a/app/routes/ra_accounts.py +++ b/app/routes/ra_accounts.py @@ -1,9 +1,9 @@ from flask import Blueprint -from middleware.token import token +from middleware.bearer import bearer bp_ra_accounts = Blueprint('reactapp_accounts', __name__) @bp_ra_accounts.route('/api/v0/accounts') def create_account(): - token = token() + token = bearer() return token From 230f959d61b6c32de007a9925808fc42eddf7e9c Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 19 Jan 2018 18:20:52 -0500 Subject: [PATCH 05/19] ADD: simple authenication check --- app/middleware/auth.py | 24 ++++++++++++++++++++++++ app/routes/ra_restricted.py | 11 ++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/middleware/auth.py b/app/middleware/auth.py index e2d66541..58bc338f 100644 --- a/app/middleware/auth.py +++ b/app/middleware/auth.py @@ -8,6 +8,8 @@ from config import AUTH0_DOMAIN, API_AUDIENCE, ALGORITHMS +from middleware.mongo import mongo_find + # Format error response and append status code def get_token_auth_header(): """Obtains the access token from the Authorization Header @@ -37,6 +39,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 """ diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index e00fef72..d744d1f1 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -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_sub_claim, requires_simple_auth from middleware.mongo import mongo_update, mongo_find bp_ra_restricted = Blueprint('reactapp_restricted', __name__) @@ -22,6 +22,15 @@ 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_private_ping(): + """A valid access token and an appropriate scope are required to access this route + """ + if requires_scope("example:scope"): + 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/accounts/update", methods=['POST']) @requires_auth def update(): From 516ccfb87ffdfb7897e6d67e4b6436f7d101f0a8 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 19 Jan 2018 18:22:28 -0500 Subject: [PATCH 06/19] ADD: simple authenication check --- app/routes/ra_restricted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index d744d1f1..ae326fd0 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -24,7 +24,7 @@ def secured_private_ping(): @bp_ra_restricted.route("/api/v0/secured/simple/ping") @requires_simple_auth -def secured_private_ping(): +def secured_simple_ping(): """A valid access token and an appropriate scope are required to access this route """ if requires_scope("example:scope"): From 445458e0d17948b506451cdabaa23273191bfaec Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 19 Jan 2018 18:38:35 -0500 Subject: [PATCH 07/19] ADD: tests for simple tokens --- app/middleware/auth.py | 7 +++++++ app/routes/ra_restricted.py | 4 +--- app/tests/test_api.py | 13 +++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/middleware/auth.py b/app/middleware/auth.py index 58bc338f..777a34e7 100644 --- a/app/middleware/auth.py +++ b/app/middleware/auth.py @@ -10,6 +10,13 @@ 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 diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index ae326fd0..159b9dc3 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -27,9 +27,7 @@ def secured_private_ping(): def secured_simple_ping(): """A valid access token and an appropriate scope are required to access this route """ - if requires_scope("example:scope"): - return "All good. You're authenticated and the access token has the appropriate scope" - return "You don't have access to this resource" + 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 diff --git a/app/tests/test_api.py b/app/tests/test_api.py index 8314aef2..a82915fe 100644 --- a/app/tests/test_api.py +++ b/app/tests/test_api.py @@ -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 '' 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) is str + + # 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" From b6cb5b63dd7de21e33fe8fb71caa90a0b2c8c75d Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 21 Jan 2018 13:07:59 -0500 Subject: [PATCH 08/19] FIX: allow unicode returns too --- app/tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/test_api.py b/app/tests/test_api.py index a82915fe..d72325cd 100644 --- a/app/tests/test_api.py +++ b/app/tests/test_api.py @@ -35,7 +35,7 @@ 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) is str + assert type(token) in (str, unicode) # Check the bearer token allows access to a protected ping. headers = { From 189e843b4b7f0ab7cf017d681d89ceab89e454c3 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Mon, 22 Jan 2018 18:28:50 -0500 Subject: [PATCH 09/19] FIX: create a blank jobs dict when generating accounts --- app/middleware/bearer.py | 2 ++ app/middleware/mongo.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/middleware/bearer.py b/app/middleware/bearer.py index 0ce86065..e1fb382e 100644 --- a/app/middleware/bearer.py +++ b/app/middleware/bearer.py @@ -23,6 +23,8 @@ def store(token): # 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() diff --git a/app/middleware/mongo.py b/app/middleware/mongo.py index 0b26de3e..b1f5fffb 100644 --- a/app/middleware/mongo.py +++ b/app/middleware/mongo.py @@ -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) From 17875097c3200d6d017bc70529b9e2e184ff1a2f Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Mon, 22 Jan 2018 21:19:34 -0500 Subject: [PATCH 10/19] FIX: only require simple auth for saving store --- app/routes/ra_restricted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index 159b9dc3..0b2c098d 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -30,7 +30,7 @@ def secured_simple_ping(): 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) From 71bab427f88d957eb1df392cb0ac67f7e3a3268b Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 23 Jan 2018 19:46:53 -0500 Subject: [PATCH 11/19] FIX: find should require simple ping too --- app/routes/ra_restricted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index 0b2c098d..a734d8ef 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -40,7 +40,7 @@ def update(): return jsonify('true') @bp_ra_restricted.route("/api/v0/secured/accounts/find") -@requires_auth +@requires_simple_auth def find(): uid = get_sub_claim() store = mongo_find(uid) From b3413e1ddca43786c5eda1baa9490e6bee84a032 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Jan 2018 12:22:33 -0500 Subject: [PATCH 12/19] FIX: use the bearer token instead of the sub claim --- app/routes/ra_restricted.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index a734d8ef..e3d6a219 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -1,5 +1,5 @@ from flask import Blueprint, request, jsonify -from middleware.auth import requires_auth, requires_scope, get_sub_claim, requires_simple_auth +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__) @@ -42,6 +42,6 @@ def update(): @bp_ra_restricted.route("/api/v0/secured/accounts/find") @requires_simple_auth def find(): - uid = get_sub_claim() + uid = get_token_auth_header store = mongo_find(uid) return jsonify(store) From 02eb67daf0b61200a0d4d3c62d9c874f8df378c8 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Jan 2018 12:27:21 -0500 Subject: [PATCH 13/19] FIX: use the bearer token instead of the sub claim --- app/routes/ra_restricted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index e3d6a219..22472dc4 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -42,6 +42,6 @@ def update(): @bp_ra_restricted.route("/api/v0/secured/accounts/find") @requires_simple_auth def find(): - uid = get_token_auth_header + uid = get_token_auth_header() store = mongo_find(uid) return jsonify(store) From 4b6bb6c86044da55a7644b476cddba3eb76e89bd Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Jan 2018 12:43:06 -0500 Subject: [PATCH 14/19] FIX: another swap to bearer token instead of sub claim --- app/routes/ra_restricted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index 22472dc4..f0deca67 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -32,7 +32,7 @@ def secured_simple_ping(): @bp_ra_restricted.route("/api/v0/secured/accounts/update", methods=['POST']) @requires_simple_auth def update(): - uid = get_sub_claim() + uid = get_token_auth_header() print('update() request:', request) json = request.json print('update()', json) From 86d0428266df4dfcad4f39d800175dee7aa0edc0 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 25 Jan 2018 14:49:35 -0500 Subject: [PATCH 15/19] ADD: hash comparisons for update --- app/middleware/mongo.py | 2 +- app/routes/ra_restricted.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/middleware/mongo.py b/app/middleware/mongo.py index b1f5fffb..6defb5ba 100644 --- a/app/middleware/mongo.py +++ b/app/middleware/mongo.py @@ -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) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index f0deca67..2d5276b2 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -35,8 +35,22 @@ def update(): uid = get_token_auth_header() print('update() request:', request) json = request.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 item.hash: + hashes.add(item.hash) + # Check the update. + for item in json: + if item.hash 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) + mongo_update(uid, previous_store) return jsonify('true') @bp_ra_restricted.route("/api/v0/secured/accounts/find") From f4413e8e8473dafc6214ca3bc6be39333171d5dd Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 25 Jan 2018 14:57:05 -0500 Subject: [PATCH 16/19] DEBUG: what is spfy seeing? --- app/routes/ra_restricted.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index 2d5276b2..35a0d999 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -33,8 +33,9 @@ def secured_simple_ping(): @requires_simple_auth def update(): uid = get_token_auth_header() - print('update() request:', request) + 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 @@ -51,7 +52,7 @@ def update(): # The previous_store should now be merged with the update. print('update()', json) mongo_update(uid, previous_store) - return jsonify('true') + return jsonify('success!') @bp_ra_restricted.route("/api/v0/secured/accounts/find") @requires_simple_auth From 312ee64bab7ccc0b6a4ae1905122a6120cbdd6cf Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 25 Jan 2018 15:02:13 -0500 Subject: [PATCH 17/19] FIX: right namespaces --- app/routes/ra_restricted.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/routes/ra_restricted.py b/app/routes/ra_restricted.py index 35a0d999..96127dfe 100644 --- a/app/routes/ra_restricted.py +++ b/app/routes/ra_restricted.py @@ -43,11 +43,11 @@ def update(): # Create a set of the hashes. for item in previous_store: # Check if the item (eg. the job) has a hash. - if item.hash: - hashes.add(item.hash) + if 'hash' in item: + hashes.add(item['hash']) # Check the update. for item in json: - if item.hash and item.hash not in hashes: + 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) From 0661e29285c47da141e2eb86c0ad956eb366843e Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 25 Jan 2018 15:16:21 -0500 Subject: [PATCH 18/19] UPDATE: reactapp to bearer accounts(6.1.0) --- reactapp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactapp b/reactapp index 4f3a886d..fc7a5b9f 160000 --- a/reactapp +++ b/reactapp @@ -1 +1 @@ -Subproject commit 4f3a886da331b617b286a5d4b20e2a2382e0d1f6 +Subproject commit fc7a5b9f1ebfa8e61b1310124352f996fa1279de From e02131e6bb21451eb362771f281ab68c7c16676d Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 25 Jan 2018 15:28:57 -0500 Subject: [PATCH 19/19] FIX: should pin reactapp to master instead --- reactapp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactapp b/reactapp index fc7a5b9f..a6b539ac 160000 --- a/reactapp +++ b/reactapp @@ -1 +1 @@ -Subproject commit fc7a5b9f1ebfa8e61b1310124352f996fa1279de +Subproject commit a6b539ac33f50d6f44f35c4eebb5c53bc5fd495f