Unverified Commit 0884eb49 authored by Dan Jones's avatar Dan Jones
Browse files

refactor: client creds grant and bearer tokens

Use client creds to issue a bearer token
Authenticate subsequent endpoints with authorization header
parent 410dde69
......@@ -12,6 +12,7 @@ flask-restful = "*"
marshmallow = "*"
bson = "*"
flask-cors = "*"
cryptography = "*"
[dev-packages]
......
{
"_meta": {
"hash": {
"sha256": "1147de24391cc36d2688e229ffc68c4a093efa85ef0a957f89206ba6784dc2b3"
"sha256": "d216458acf3005c6f459eb1d74f7d5bbda1a7b5e1e042efe855739943c4674d4"
},
"pipfile-spec": 6,
"requires": {
......@@ -38,6 +38,75 @@
"index": "pypi",
"version": "==0.5.10"
},
"cffi": {
"hashes": [
"sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5",
"sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef",
"sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104",
"sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426",
"sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405",
"sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375",
"sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a",
"sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e",
"sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc",
"sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf",
"sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185",
"sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497",
"sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3",
"sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35",
"sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c",
"sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83",
"sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21",
"sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca",
"sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984",
"sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac",
"sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd",
"sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee",
"sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a",
"sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2",
"sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192",
"sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7",
"sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585",
"sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f",
"sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e",
"sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27",
"sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b",
"sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e",
"sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e",
"sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d",
"sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c",
"sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415",
"sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82",
"sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02",
"sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314",
"sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325",
"sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c",
"sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3",
"sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914",
"sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045",
"sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d",
"sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9",
"sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5",
"sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2",
"sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c",
"sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3",
"sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2",
"sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8",
"sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d",
"sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d",
"sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9",
"sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162",
"sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76",
"sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4",
"sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e",
"sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9",
"sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6",
"sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b",
"sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01",
"sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"
],
"version": "==1.15.1"
},
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
......@@ -46,6 +115,38 @@
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"cryptography": {
"hashes": [
"sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d",
"sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd",
"sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146",
"sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7",
"sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436",
"sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0",
"sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828",
"sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b",
"sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55",
"sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36",
"sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50",
"sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2",
"sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a",
"sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8",
"sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0",
"sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548",
"sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320",
"sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748",
"sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249",
"sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959",
"sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f",
"sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0",
"sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd",
"sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220",
"sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c",
"sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722"
],
"index": "pypi",
"version": "==38.0.3"
},
"flask": {
"hashes": [
"sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b",
......@@ -87,11 +188,11 @@
},
"importlib-metadata": {
"hashes": [
"sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab",
"sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"
"sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b",
"sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"
],
"markers": "python_version < '3.10'",
"version": "==5.0.0"
"version": "==5.1.0"
},
"itsdangerous": {
"hashes": [
......@@ -195,6 +296,13 @@
"index": "pypi",
"version": "==2.3.0"
},
"pycparser": {
"hashes": [
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
"sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
],
"version": "==2.21"
},
"pyparsing": {
"hashes": [
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
......
from flask import Flask
from flask_restful import Api
from endpoints.hello import HelloWorld
from endpoints.clients import Client, ClientList
from endpoints.receive import Receive
from endpoints.send import Send
from endpoints.notify import Notify
from endpoints.token import Token
from flask_cors import CORS
from models.token import TokenModel
token = TokenModel()
token.setSecret()
app = Flask(__name__)
api = Api(app)
CORS(app, resources={r"*": {"origins": "http://localhost:8086"}})
api.add_resource(HelloWorld, "/")
api.add_resource(ClientList, "/client")
api.add_resource(Client, "/client/<client_id>")
api.add_resource(Receive, "/receive")
api.add_resource(Send, "/send")
api.add_resource(Notify, "/notify")
api.add_resource(Token, "/token")
if __name__ == "__main__":
app.run(debug=True, port=8087)
import json
from flask_restful import Resource, abort
from models.token import TokenModel
class AuthResource(Resource):
def __init__(self):
self.token = TokenModel()
with open("clients.json", "r") as clients_file:
self.clients = json.load(clients_file)
def auth(self, request):
allow = False
auth = request.headers.get('Authorization', False)
if auth:
token = auth.split(' ').pop()
parsed = self.token.validate(token)
if parsed['valid']:
client = self.clients.get(parsed['client_id'])
if client:
self.client = client
allow = True
if not allow:
abort(403, message="Invalid token")
return allow
\ No newline at end of file
from flask_restful import Resource, request, abort
import json
from flask_restful import request, abort
from marshmallow import Schema, fields
import pika
import json
from endpoints.auth_resource import AuthResource
class NotifySchema(Schema):
client_id = fields.Str(required=True)
secret = fields.Str(required=True)
body = fields.Str(required=True)
class Notify(Resource):
class Notify(AuthResource):
clients = None
schema = None
def __init__(self):
super().__init__()
self.schema = NotifySchema()
with open("clients.json", "r") as clients_file:
self.clients = json.load(clients_file)
def post(self):
args = request.get_json()
errors = self.schema.validate(args)
......@@ -29,14 +28,11 @@ class Notify(Resource):
'topic': 'broadcast',
'message': body,
}
client_id = args.get("client_id")
notify_queue = client_id + "-broadcast"
if client_id in self.clients:
client = self.clients.get(client_id)
if args.get("secret") == client.get("secret"):
allow = True
allow = self.auth(request)
if allow:
notify_queue = self.client['client_id'] + "-broadcast"
connection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))
channel = connection.channel()
channel.queue_declare(queue=notify_queue, durable=True)
......
from flask_restful import Resource, request, abort
from flask_restful import request, abort
from marshmallow import Schema, fields
import pika
import json
from models.token import TokenModel
from endpoints.auth_resource import AuthResource
class ReceiveQuerySchema(Schema):
client_id = fields.Str(required=True)
secret = fields.Str(required=True)
max_messages = fields.Int(required=False)
class Receive(Resource):
class Receive(AuthResource):
clients = None
schema = None
def __init__(self):
super().__init__()
self.schema = ReceiveQuerySchema()
with open("clients.json", "r") as clients_file:
self.clients = json.load(clients_file)
def get(self):
errors = self.schema.validate(request.args)
......@@ -25,15 +24,12 @@ class Receive(Resource):
abort(400, message=str(errors))
messages = []
allow = False
max_messages = request.args.get("max_messages", 10)
client_id = request.args.get("client_id")
inbox_queue = client_id + "-inbox"
if client_id in self.clients:
client = self.clients.get(client_id)
if request.args.get("secret") == client.get("secret"):
allow = True
allow = self.auth(request)
if allow:
inbox_queue = self.client['client_id'] + "-inbox"
if allow:
connection = pika.BlockingConnection(
pika.ConnectionParameters(host="localhost")
......@@ -51,7 +47,4 @@ class Receive(Resource):
break
channel.close()
connection.close()
else:
abort(403, message="Invalid client credentials")
return messages
from flask_restful import Resource, request, abort
import json
from flask_restful import request, abort
from marshmallow import Schema, fields
import pika
import json
from endpoints.auth_resource import AuthResource
class SendSchema(Schema):
client_id = fields.Str(required=True)
secret = fields.Str(required=True)
body = fields.Str(required=True)
topic = fields.Str(required=True)
class Send(Resource):
class Send(AuthResource):
clients = None
schema = None
def __init__(self):
super().__init__()
self.schema = SendSchema()
with open("clients.json", "r") as clients_file:
self.clients = json.load(clients_file)
def post(self):
args = request.get_json()
errors = self.schema.validate(args)
if errors:
abort(400, message=str(errors))
allow = False
body = args.get("body")
topic = args.get("topic")
client_id = args.get("client_id")
outbox_queue = client_id + "-outbox"
if client_id in self.clients:
client = self.clients.get(client_id)
if args.get("secret") == client.get("secret"):
allow = True
allow = self.auth(request)
if allow:
body = args.get("body")
topic = args.get("topic")
outbox_queue = self.client['client_id'] + "-outbox"
connection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))
channel = connection.channel()
channel.queue_declare(queue=outbox_queue, durable=True)
......
import json
from flask_restful import Resource, request, abort
from marshmallow import Schema, fields
import pika
from models.token import TokenModel
class TokenQuerySchema(Schema):
client_id = fields.Str(required=True)
secret = fields.Str(required=True)
class Token(Resource):
clients = None
schema = None
model = None
def __init__(self):
self.schema = TokenQuerySchema()
self.model = TokenModel()
with open("clients.json", "r") as clients_file:
self.clients = json.load(clients_file)
def get(self):
errors = self.schema.validate(request.args)
if errors:
abort(400, message=str(errors))
token = None
allow = False
max_messages = request.args.get("max_messages", 10)
client_id = request.args.get("client_id")
if client_id in self.clients:
client = self.clients.get(client_id)
if request.args.get("secret") == client.get("secret"):
allow = True
if allow:
token = self.model.get(client_id)
else:
abort(403, message="Invalid client credentials")
return token
\ No newline at end of file
from cryptography.fernet import Fernet,InvalidToken
import datetime
import os
import json
TOKENS = {}
class TokenModel():
clients = None
schema = None
key = None
fernet = None
token_lifetime_hours = None
env_lifetime = 'SOAR_TOKEN_LIFETIME'
env_secret = 'SOAR_TOKEN_SECRET'
def __init__(self):
self.getFernet()
self.token_lifetime_hours = os.getenv(self.env_lifetime, 24)
def getFernet(self):
self.fernet = Fernet(self.getKey().encode())
def getKey(self):
key = os.getenv(self.env_secret)
print(key)
if not key:
key = Fernet.generate_key().decode()
os.environ[self.env_secret] = key
self.key = key
return self.key
def setSecret(self):
if not os.getenv(self.env_secret):
os.environ[self.env_secret] = self.getKey()
def getExpiry(self):
now = datetime.datetime.utcnow()
expires = now + datetime.timedelta(hours=self.token_lifetime_hours)
return expires.isoformat()
def encrypt(self, client_id):
try:
expiry = self.getExpiry()
token_content = {
'client_id': client_id,
'expiry': expiry
}
token = self.fernet.encrypt(json.dumps(token_content).encode()).decode()
print(f"Encode: {token}")
return {
'token': token,
'expiry': expiry
}
except KeyError as e:
return None
def decrypt(self, token):
try:
print(f"Decode: {token}")
content = json.loads(self.fernet.decrypt(token.encode()).decode())
return content
except (InvalidToken,KeyError) as e:
return None
def get(self, client_id):
response = self.encrypt(client_id)
TOKENS[response['token']] = client_id
return response
def validate(self, token):
response = {
'valid': False
}
if token in TOKENS:
content = self.decrypt(token)
if content:
now = datetime.datetime.utcnow()
expires = datetime.datetime.fromisoformat(content['expiry'])
response['valid'] = expires > now
if response['valid']:
response.update(content)
else:
del TOKENS[token]
else:
del TOKENS[token]
return response
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment