From ce8893aca006572fe9da0af6e736d1dd57fc61a5 Mon Sep 17 00:00:00 2001 From: Rodrigo Seiji Piubeli Hirao Date: Fri, 26 Jul 2024 13:51:45 +0100 Subject: [PATCH 1/2] Feat: validation --- api/__init__.py | 46 +++++++- api/config.py | 5 + api/utils/auth/__init__.py | 0 api/utils/auth/validator.py | 22 ++++ poetry.lock | 212 ++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 + 6 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 api/utils/auth/__init__.py create mode 100644 api/utils/auth/validator.py diff --git a/api/__init__.py b/api/__init__.py index ea378d6..bbfb32e 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -4,6 +4,7 @@ from importlib import import_module from flask_sqlalchemy import SQLAlchemy from flask_openai import OpenAI + from api.config import Config from api.models.db.cycle import Cycle from api.models.db.key_result_check_mark import KeyResultCheckMark @@ -13,8 +14,36 @@ from api.models.db.key_result import KeyResult from api.models.db.objective import Objective +from authlib.integrations.flask_client import OAuth +from authlib.integrations.flask_oauth2 import ResourceProtector + +from api.config import Config +from api.utils.auth.validator import Auth0JWTBearerTokenValidator + core_db = SQLAlchemy() core_openai = OpenAI() +oauth = OAuth() +require_auth = ResourceProtector() + + +def create_auth(config: Config): + validator = Auth0JWTBearerTokenValidator( + config.AUTH0_DOMAIN, + config.AUTH0_AUDIENCE + ) + require_auth.register_token_validator(validator) + + oauth.register( + "auth0", + client_id=config.AUTH0_CLIENT_ID, + client_secret=config.AUTH0_CLIENT_SECRET, + client_kwargs={ + "scope": "openid profile email", + }, + server_metadata_url=f'https://{ + config.AUTH0_DOMAIN}/.well-known/openid-configuration' + ) + dictConfig({ 'version': 1, @@ -40,10 +69,19 @@ def create_app(config=Config()): logging.getLogger('sqlalchemy.orm').setLevel(logging.INFO) app = Flask(__name__) - app.config.from_object(config) - core_db.init_app(app) - core_openai.init_app(app) - for module_name in ('llm',): + + +<< << << < HEAD +== == == = +config = Config() +>>>>>> > 46711a0(Feat: validation) +app.config.from_object(config) +core_db.init_app(app) +core_openai.init_app(app) + oauth.init_app(app) + create_auth(config) + + for module_name in ('llm',): module = import_module( 'api.services.{}.routes'.format(module_name)) app.register_blueprint(module.blueprint) diff --git a/api/config.py b/api/config.py index 68903bc..cb58a72 100644 --- a/api/config.py +++ b/api/config.py @@ -17,6 +17,11 @@ class Config(object): OPENAI_API_KEY = os.environ['OPENAI_API_KEY'] + AUTH0_CLIENT_ID = os.environ['AUTH0_CLIENT_ID'] + AUTH0_CLIENT_SECRET = os.environ['AUTH0_CLIENT_SECRET'] + AUTH0_DOMAIN = os.environ['AUTH0_DOMAIN'] + AUTH0_AUDIENCE = os.environ['AUTH0_AUDIENCE'] + DEBUG = (os.getenv('DEBUG', 'False') == 'True') if not DEBUG: # Security diff --git a/api/utils/auth/__init__.py b/api/utils/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/utils/auth/validator.py b/api/utils/auth/validator.py new file mode 100644 index 0000000..aa42974 --- /dev/null +++ b/api/utils/auth/validator.py @@ -0,0 +1,22 @@ +import json +from urllib.request import urlopen + +from authlib.oauth2.rfc7523 import JWTBearerTokenValidator +from authlib.jose.rfc7517.jwk import JsonWebKey + + +class Auth0JWTBearerTokenValidator(JWTBearerTokenValidator): + def __init__(self, domain, audience): + issuer = f"https://{domain}/" + jsonurl = urlopen(f"{issuer}.well-known/jwks.json") + public_key = JsonWebKey.import_key_set( + json.loads(jsonurl.read()) + ) + super(Auth0JWTBearerTokenValidator, self).__init__( + public_key + ) + self.claims_options = { + "exp": {"essential": True}, + "aud": {"essential": True, "value": audience}, + "iss": {"essential": True, "value": issuer}, + } diff --git a/poetry.lock b/poetry.lock index 93b5c25..c1e659f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -72,6 +72,169 @@ files = [ {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + [[package]] name = "click" version = "8.1.7" @@ -567,6 +730,17 @@ files = [ {file = "psycopg2-2.9.9.tar.gz", hash = "sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156"}, ] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pydantic" version = "2.8.2" @@ -742,6 +916,27 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "sniffio" version = "1.3.1" @@ -893,6 +1088,23 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "werkzeug" version = "3.0.3" diff --git a/pyproject.toml b/pyproject.toml index d54970a..b7ec94b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ Flask = "^3.0.3" flask-sqlalchemy = "^3.1.1" psycopg2 = "^2.9.9" flask-openai = "^0.5.0" +requests = "^2.32.3" +authlib = "^1.3.1" [tool.poetry.group.dev.dependencies] pytest = "^8.2.2" From 9af25aa81746be4c43115a52c6b7de1b093580bb Mon Sep 17 00:00:00 2001 From: Rodrigo Seiji Piubeli Hirao Date: Mon, 5 Aug 2024 23:00:51 +0100 Subject: [PATCH 2/2] Feat: auth by entity. --- api/__init__.py | 45 ++---- api/config.py | 2 + api/models/db/__init__.py | 20 +++ .../db/associations}/__init__.py | 0 .../db/associations/association_team_user.py | 10 ++ api/models/db/cycle.py | 4 + api/models/db/key_result_check_in.py | 8 ++ api/models/db/key_result_check_mark.py | 12 ++ api/models/db/key_result_comment.py | 8 ++ api/models/db/team.py | 11 ++ api/models/db/user.py | 7 + api/models/db/views/__init__.py | 0 api/models/db/views/team_company.py | 12 ++ api/services/auth/__init__.py | 9 ++ api/services/auth/routes.py | 10 ++ api/services/llm/routes.py | 15 +- api/utils/auth.py | 135 ++++++++++++++++++ api/utils/auth/validator.py | 22 --- server exited unexpectedly | 2 +- 19 files changed, 270 insertions(+), 62 deletions(-) rename api/{utils/auth => models/db/associations}/__init__.py (100%) create mode 100644 api/models/db/associations/association_team_user.py create mode 100644 api/models/db/views/__init__.py create mode 100644 api/models/db/views/team_company.py create mode 100644 api/services/auth/__init__.py create mode 100644 api/services/auth/routes.py create mode 100644 api/utils/auth.py delete mode 100644 api/utils/auth/validator.py diff --git a/api/__init__.py b/api/__init__.py index bbfb32e..856884c 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -6,39 +6,22 @@ from flask_openai import OpenAI from api.config import Config -from api.models.db.cycle import Cycle -from api.models.db.key_result_check_mark import KeyResultCheckMark -from api.models.db.team import Team -from api.models.db.types.cycle_cadence_enum import CycleCadenceEnum -from api.models.db.user import User -from api.models.db.key_result import KeyResult -from api.models.db.objective import Objective from authlib.integrations.flask_client import OAuth -from authlib.integrations.flask_oauth2 import ResourceProtector -from api.config import Config -from api.utils.auth.validator import Auth0JWTBearerTokenValidator core_db = SQLAlchemy() core_openai = OpenAI() -oauth = OAuth() -require_auth = ResourceProtector() +core_oauth = OAuth() def create_auth(config: Config): - validator = Auth0JWTBearerTokenValidator( - config.AUTH0_DOMAIN, - config.AUTH0_AUDIENCE - ) - require_auth.register_token_validator(validator) - - oauth.register( - "auth0", + core_oauth.register( + 'auth0', client_id=config.AUTH0_CLIENT_ID, client_secret=config.AUTH0_CLIENT_SECRET, client_kwargs={ - "scope": "openid profile email", + 'scope': 'openid profile email offline_access', }, server_metadata_url=f'https://{ config.AUTH0_DOMAIN}/.well-known/openid-configuration' @@ -69,20 +52,16 @@ def create_app(config=Config()): logging.getLogger('sqlalchemy.orm').setLevel(logging.INFO) app = Flask(__name__) + app.config.from_object(config) + core_db.init_app(app) + core_openai.init_app(app) + core_oauth.init_app(app) + create_auth(config) - -<< << << < HEAD -== == == = -config = Config() ->>>>>> > 46711a0(Feat: validation) -app.config.from_object(config) -core_db.init_app(app) -core_openai.init_app(app) - oauth.init_app(app) - create_auth(config) - - for module_name in ('llm',): + for module_name in ('llm', 'auth'): module = import_module( 'api.services.{}.routes'.format(module_name)) app.register_blueprint(module.blueprint) + print(app.url_map) + return app diff --git a/api/config.py b/api/config.py index cb58a72..b576894 100644 --- a/api/config.py +++ b/api/config.py @@ -2,6 +2,8 @@ class Config(object): + SECRET_KEY = os.environ['SECRET_KEY'] + SESSION_TYPE = 'cachelib' basedir = os.path.abspath(os.path.dirname(__file__)) diff --git a/api/models/db/__init__.py b/api/models/db/__init__.py index e69de29..85a55d9 100644 --- a/api/models/db/__init__.py +++ b/api/models/db/__init__.py @@ -0,0 +1,20 @@ +from api.models.db.cycle import Cycle +from api.models.db.key_result_check_in import KeyResultCheckIn +from api.models.db.key_result_check_mark import KeyResultCheckMark +from api.models.db.key_result_comment import KeyResultComment +from api.models.db.team import Team +from api.models.db.user import User +from api.models.db.key_result import KeyResult +from api.models.db.objective import Objective + + +DB_MAP = { + 'team': Team, + 'user': User, + 'cycle': Cycle, + 'objective': Objective, + 'key-result': KeyResult, + 'key-result-check-mark': KeyResultCheckMark, + 'key-result-check_in': KeyResultCheckIn, + 'key-result-comment': KeyResultComment, +} diff --git a/api/utils/auth/__init__.py b/api/models/db/associations/__init__.py similarity index 100% rename from api/utils/auth/__init__.py rename to api/models/db/associations/__init__.py diff --git a/api/models/db/associations/association_team_user.py b/api/models/db/associations/association_team_user.py new file mode 100644 index 0000000..021e069 --- /dev/null +++ b/api/models/db/associations/association_team_user.py @@ -0,0 +1,10 @@ +from sqlalchemy import Column, ForeignKey, Table + +from api.models.db.base import Base + +association_team_user = Table( + 'team_users_user', + Base.metadata, + Column('user_id', ForeignKey('user.id')), + Column('team_id', ForeignKey('team.id')), +) diff --git a/api/models/db/cycle.py b/api/models/db/cycle.py index 5fab12a..4ce5e5e 100644 --- a/api/models/db/cycle.py +++ b/api/models/db/cycle.py @@ -30,3 +30,7 @@ class Cycle(Base): objectives: Mapped[List['Objective']] = relationship( back_populates='cycle') + + @property + def owner_id(self): + return self.team.owner_id diff --git a/api/models/db/key_result_check_in.py b/api/models/db/key_result_check_in.py index 3811a68..39363b8 100644 --- a/api/models/db/key_result_check_in.py +++ b/api/models/db/key_result_check_in.py @@ -22,3 +22,11 @@ class KeyResultCheckIn(Base): key_result_id: Mapped[str] = mapped_column(ForeignKey('key_result.id')) key_result: Mapped['KeyResult'] = relationship( back_populates='key_result_check_ins') + + @property + def team_id(self): + return self.key_result.team_id + + @property + def team(self): + return self.key_result.team diff --git a/api/models/db/key_result_check_mark.py b/api/models/db/key_result_check_mark.py index 3bbcde1..92673e5 100644 --- a/api/models/db/key_result_check_mark.py +++ b/api/models/db/key_result_check_mark.py @@ -26,3 +26,15 @@ class KeyResultCheckMark(Base): ForeignKey('key_result.id')) key_result: Mapped['KeyResult'] = relationship( back_populates='key_result_check_marks') + + @property + def team_id(self): + return self.key_result.team_id + + @property + def team(self): + return self.key_result.team + + @property + def owner_id(self): + return self.assigned_user_id diff --git a/api/models/db/key_result_comment.py b/api/models/db/key_result_comment.py index aca495d..0583a52 100644 --- a/api/models/db/key_result_comment.py +++ b/api/models/db/key_result_comment.py @@ -23,3 +23,11 @@ class KeyResultComment(Base): ForeignKey('key_result.id')) key_result: Mapped['KeyResult'] = relationship( back_populates='key_result_comments') + + @property + def team_id(self): + return self.key_result.team_id + + @property + def team(self): + return self.key_result.team diff --git a/api/models/db/team.py b/api/models/db/team.py index 460ab9e..ab9c026 100644 --- a/api/models/db/team.py +++ b/api/models/db/team.py @@ -3,6 +3,8 @@ from sqlalchemy.orm import relationship from typing import TYPE_CHECKING, Optional, List +from api.models.db.associations.association_team_user import association_team_user +from api.models.db.views.team_company import association_team_company from api.models.db.base import Base if TYPE_CHECKING: from api.models.db.key_result import KeyResult @@ -30,3 +32,12 @@ class Team(Base): objectives: Mapped[List['Objective']] = relationship(back_populates='team') key_results: Mapped[List['KeyResult'] ] = relationship(back_populates='team') + + users: Mapped[List['User']] = relationship( + secondary=association_team_user, back_populates='teams') + + company: Mapped['Team'] = relationship( + secondary=association_team_company, + primaryjoin=id == association_team_company.c.team_id, + secondaryjoin=id == association_team_company.c.company_id, + ) diff --git a/api/models/db/user.py b/api/models/db/user.py index 6a9289f..a953ee8 100644 --- a/api/models/db/user.py +++ b/api/models/db/user.py @@ -2,8 +2,10 @@ from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import relationship +from api.models.db.associations.association_team_user import association_team_user from api.models.db.base import Base if TYPE_CHECKING: + from api.models.db.team import Team from api.models.db.key_result import KeyResult from api.models.db.objective import Objective from api.models.db.key_result_check_in import KeyResultCheckIn @@ -17,6 +19,8 @@ class User(Base): first_name: Mapped[str] = mapped_column() last_name: Mapped[str] = mapped_column() + authz_sub: Mapped[str] = mapped_column() + objectives: Mapped[List['Objective']] = relationship( back_populates='owner') key_results: Mapped[List['KeyResult'] @@ -27,3 +31,6 @@ class User(Base): back_populates='assigned_user') key_result_comments: Mapped[List['KeyResultComment']] = relationship( back_populates='user') + + teams: Mapped[List['Team']] = relationship( + secondary=association_team_user, back_populates='users') diff --git a/api/models/db/views/__init__.py b/api/models/db/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models/db/views/team_company.py b/api/models/db/views/team_company.py new file mode 100644 index 0000000..77f8217 --- /dev/null +++ b/api/models/db/views/team_company.py @@ -0,0 +1,12 @@ +# Define the TeamCompany model representing the view +from sqlalchemy import Column, ForeignKey, Table +from api.models.db.base import Base + + +association_team_company = Table( + 'team_company', + Base.metadata, + Column('team_id', ForeignKey('team.id')), + Column('company_id', ForeignKey('team.id')), + Column('depth'), +) diff --git a/api/services/auth/__init__.py b/api/services/auth/__init__.py new file mode 100644 index 0000000..bb5147a --- /dev/null +++ b/api/services/auth/__init__.py @@ -0,0 +1,9 @@ +from flask import Blueprint + +blueprint = Blueprint( + 'auth', + __name__, + url_prefix='/core/auth', + template_folder='templates', + static_folder='static' +) diff --git a/api/services/auth/routes.py b/api/services/auth/routes.py new file mode 100644 index 0000000..3e8e053 --- /dev/null +++ b/api/services/auth/routes.py @@ -0,0 +1,10 @@ +from flask import session, redirect +from api import core_oauth +from . import blueprint + + +@blueprint.route('/callback', methods=['GET', 'POST']) +def callback(): + token = core_oauth.auth0.authorize_access_token() # type: ignore + session['user'] = token + return redirect('/') diff --git a/api/services/llm/routes.py b/api/services/llm/routes.py index 69b47e6..d72b700 100644 --- a/api/services/llm/routes.py +++ b/api/services/llm/routes.py @@ -1,15 +1,18 @@ from api.services.llm.logic.index import LlmIndex from api.services.llm.logic.summary import LlmSummary +from api.utils.auth import requires_auth from api.utils.openai import OpenAI from . import blueprint from flask import render_template -@blueprint.route('/') -def index(okr_id: str): - return render_template('index.html', opts=LlmIndex(okr_id)) +@blueprint.route('/') +@requires_auth('key-result:create') +def index(key_result_id: str): + return render_template('index.html', opts=LlmIndex(key_result_id)) -@blueprint.route('/summary/') -def summary(okr_id: str): - return render_template('summary.html', opts=LlmSummary(OpenAI(), okr_id)) +@blueprint.route('/summary/') +@requires_auth('key-result:create') +def summary(key_result_id: str): + return render_template('summary.html', opts=LlmSummary(OpenAI(), key_result_id)) diff --git a/api/utils/auth.py b/api/utils/auth.py new file mode 100644 index 0000000..47e4c29 --- /dev/null +++ b/api/utils/auth.py @@ -0,0 +1,135 @@ +from functools import wraps +import jwt +import base64 + +from flask import abort, request, session, url_for +from sqlalchemy.orm import joinedload +from api import core_oauth, core_db +from api.models.db import DB_MAP +from api.models.db.team import Team +from api.models.db.user import User + + +SCOPES = ['company', 'team', 'owns'] + + +class PermissionController: + '''Controller to check if user has minimum permissions to selected entity + + Attributes: + sub: authz_sub as on database + model: entity model as defined on models.DB_MAP + entity_id: id to search for on table column `id` + ''' + + def __init__(self, sub: str, entity: str, entity_id: str): + '''Initializes controller with user sub and entity + + Args: + sub: authz_sub as on database + entity: entity table name + entity_id: id to search for on table column `id` + ''' + self.entity_id = entity_id + self.model = DB_MAP[entity] + self.sub = sub + + def verify(self, scope: str) -> bool: + '''Verifies if current user has permission to access entity + based on scope + + Args: + scope: auth scope in 'entity:action:scope' + + Returns: + True if user has permission + ''' + if scope == 'owns': + return self._user_owns_entity() + if scope == 'team': + return self._user_team_owns_entity() + if scope == 'company': + return self._user_company_owns_entity() + return False + + def _user_owns_entity(self) -> bool: + '''Verifies if current entity has owner_id as the user id''' + cur_user: User | None = core_db.session.query( + User + ).filter_by( + authz_sub=self.sub + ).one() + cur_obj = core_db.session.query( + self.model).filter_by(id=self.entity_id).one() + + return cur_user.id == cur_obj.owner_id + + def _user_team_owns_entity(self) -> bool: + '''Verifies if current entity has team_id as the user team id''' + cur_user: User | None = core_db.session.query(User).filter_by( + authz_sub=self.sub + ).options( + joinedload(User.teams), + ).one() + cur_obj = core_db.session.query( + self.model).filter_by(id=self.entity_id).one() + + for team in cur_user.teams: + if team.id == cur_obj.team_id: + return True + return False + + def _user_company_owns_entity(self) -> bool: + '''Verifies if current entity has team.company_id + as the user company id''' + cur_user: User | None = core_db.session.query(User).filter_by( + authz_sub=self.sub + ).options( + joinedload(User.teams), + joinedload(User.teams).joinedload(Team.company), + ).one() + cur_obj = core_db.session.query(self.model).filter_by( + id=self.entity_id + ).one() + + for team in cur_user.teams: + if team.company.id == cur_obj.team.company.id: + return True + return False + + +def requires_auth(permission=None): + ''' + Use on routes that require a valid session, otherwise it aborts with a 403 + ''' + def _decorator(f): + @wraps(f) + def wrapper(*args, **kwargs): + user = session.get('user') + print(user) + if user is None: + return core_oauth.auth0.authorize_redirect( # type: ignore + redirect_uri=url_for('auth.callback', _external=True) + ) + if permission is not None: + # Get user permissions + granted_permissions = [] + for p in user['userinfo']["'https://api.getbud.co'/permissions"]: + if p.startswith(permission): + granted_permissions.append(p) + granted_scope = '' + for scope in SCOPES: + if f'{permission}:{scope}' in granted_permissions: + granted_scope = scope + break + entity, _ = permission.split(':') + entity_id_param = f'{entity.replace('-', '_')}_id' + + controller = PermissionController( + user['userinfo']['sub'], entity, kwargs[entity_id_param]) + if not controller.verify(granted_scope): + return abort(403) + + return f(*args, **kwargs) + return wrapper + return _decorator diff --git a/api/utils/auth/validator.py b/api/utils/auth/validator.py deleted file mode 100644 index aa42974..0000000 --- a/api/utils/auth/validator.py +++ /dev/null @@ -1,22 +0,0 @@ -import json -from urllib.request import urlopen - -from authlib.oauth2.rfc7523 import JWTBearerTokenValidator -from authlib.jose.rfc7517.jwk import JsonWebKey - - -class Auth0JWTBearerTokenValidator(JWTBearerTokenValidator): - def __init__(self, domain, audience): - issuer = f"https://{domain}/" - jsonurl = urlopen(f"{issuer}.well-known/jwks.json") - public_key = JsonWebKey.import_key_set( - json.loads(jsonurl.read()) - ) - super(Auth0JWTBearerTokenValidator, self).__init__( - public_key - ) - self.claims_options = { - "exp": {"essential": True}, - "aud": {"essential": True, "value": audience}, - "iss": {"essential": True, "value": issuer}, - } diff --git a/server exited unexpectedly b/server exited unexpectedly index 19fa523..5ce4b79 100644 --- a/server exited unexpectedly +++ b/server exited unexpectedly @@ -1 +1 @@ -Ptmux;_Ga=d,d=a,q=2\\ \ No newline at end of file +Ptmux;_Gq=2,a=d,d=a\\ \ No newline at end of file