From 2281d3265423d01e60f8cc088de5a5730bb8a910 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sat, 15 May 2021 13:09:06 +0200 Subject: api: Migrate to Flask Restful This change updates the API to use Flask Restful instead of our own in-house REST library. This change reduces the maintenance effort and allows us to drastically simplify the API implementation needed for the OpenDC v2 API. --- opendc-web/opendc-web-api/opendc/exts.py | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 opendc-web/opendc-web-api/opendc/exts.py (limited to 'opendc-web/opendc-web-api/opendc/exts.py') diff --git a/opendc-web/opendc-web-api/opendc/exts.py b/opendc-web/opendc-web-api/opendc/exts.py new file mode 100644 index 00000000..f088a29c --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/exts.py @@ -0,0 +1,60 @@ +import os +from functools import wraps + +from flask import g, _request_ctx_stack +from werkzeug.local import LocalProxy + +from opendc.database import Database +from opendc.auth import AuthContext, AsymmetricJwtAlgorithm, get_token + + +def get_db(): + """ + Return the configured database instance for the application. + """ + _db = getattr(g, 'db', None) + if _db is None: + _db = Database.from_credentials(user=os.environ['OPENDC_DB_USERNAME'], + password=os.environ['OPENDC_DB_PASSWORD'], + database=os.environ['OPENDC_DB'], + host=os.environ.get('OPENDC_DB_HOST', 'localhost')) + g.db = _db + return _db + + +db = LocalProxy(get_db) + + +def get_auth_context(): + """ + Return the configured auth context for the application. + """ + _auth_context = getattr(g, 'auth_context', None) + if _auth_context is None: + _auth_context = AuthContext( + alg=AsymmetricJwtAlgorithm(jwks_url=f"https://{os.environ['AUTH0_DOMAIN']}/.well-known/jwks.json"), + issuer=f"https://{os.environ['AUTH0_DOMAIN']}/", + audience=os.environ['AUTH0_AUDIENCE'] + ) + g.auth_context = _auth_context + return _auth_context + + +auth_context = LocalProxy(get_auth_context) + + +def requires_auth(f): + """Decorator to determine if the Access Token is valid. + """ + + @wraps(f) + def decorated(*args, **kwargs): + token = get_token() + payload = auth_context.validate(token) + _request_ctx_stack.top.current_user = payload + return f(*args, **kwargs) + + return decorated + + +current_user = LocalProxy(lambda: getattr(_request_ctx_stack.top, 'current_user', None)) -- cgit v1.2.3 From 45b73e4683cce35de79117c5b4a6919556d9644f Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 2 Jul 2021 14:26:23 +0200 Subject: api: Add stricter validation of input/output data This change adds stricter validation of data that enters and leaves the database. As a result, we clearly separate the database model from the data model that the REST API exports. --- opendc-web/opendc-web-api/opendc/exts.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'opendc-web/opendc-web-api/opendc/exts.py') diff --git a/opendc-web/opendc-web-api/opendc/exts.py b/opendc-web/opendc-web-api/opendc/exts.py index f088a29c..d24f7197 100644 --- a/opendc-web/opendc-web-api/opendc/exts.py +++ b/opendc-web/opendc-web-api/opendc/exts.py @@ -34,8 +34,7 @@ def get_auth_context(): _auth_context = AuthContext( alg=AsymmetricJwtAlgorithm(jwks_url=f"https://{os.environ['AUTH0_DOMAIN']}/.well-known/jwks.json"), issuer=f"https://{os.environ['AUTH0_DOMAIN']}/", - audience=os.environ['AUTH0_AUDIENCE'] - ) + audience=os.environ['AUTH0_AUDIENCE']) g.auth_context = _auth_context return _auth_context @@ -46,7 +45,6 @@ auth_context = LocalProxy(get_auth_context) def requires_auth(f): """Decorator to determine if the Access Token is valid. """ - @wraps(f) def decorated(*args, **kwargs): token = get_token() -- cgit v1.2.3 From fa7ffd9d1594a5bc9dba4fc65af0a4100988341b Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 2 Jul 2021 16:47:40 +0200 Subject: api: Restrict API scopes This change adds support for restricting API scopes in the OpenDC API server. This is necessary to make a distinction between runners and regular users. --- opendc-web/opendc-web-api/opendc/exts.py | 36 +++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'opendc-web/opendc-web-api/opendc/exts.py') diff --git a/opendc-web/opendc-web-api/opendc/exts.py b/opendc-web/opendc-web-api/opendc/exts.py index d24f7197..17dacd5e 100644 --- a/opendc-web/opendc-web-api/opendc/exts.py +++ b/opendc-web/opendc-web-api/opendc/exts.py @@ -2,10 +2,11 @@ import os from functools import wraps from flask import g, _request_ctx_stack +from jose import jwt from werkzeug.local import LocalProxy from opendc.database import Database -from opendc.auth import AuthContext, AsymmetricJwtAlgorithm, get_token +from opendc.auth import AuthContext, AsymmetricJwtAlgorithm, get_token, AuthError def get_db(): @@ -56,3 +57,36 @@ def requires_auth(f): current_user = LocalProxy(lambda: getattr(_request_ctx_stack.top, 'current_user', None)) + + +def has_scope(required_scope): + """Determines if the required scope is present in the Access Token + Args: + required_scope (str): The scope required to access the resource + """ + token = get_token() + unverified_claims = jwt.get_unverified_claims(token) + if unverified_claims.get("scope"): + token_scopes = unverified_claims["scope"].split() + for token_scope in token_scopes: + if token_scope == required_scope: + return True + return False + + +def requires_scope(required_scope): + """Determines if the required scope is present in the Access Token + Args: + required_scope (str): The scope required to access the resource + """ + def decorator(f): + @wraps(f) + def decorated(*args, **kwargs): + if not has_scope(required_scope): + raise AuthError({ + "code": "Unauthorized", + "description": "You don't have access to this resource" + }, 403) + return f(*args, **kwargs) + return decorated + return decorator -- cgit v1.2.3 From 5ec19973eb3d23046d874b097275857a58c23082 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 7 Jul 2021 20:45:06 +0200 Subject: api: Add endpoints for accessing project relations This change adds additional endpoints to the REST API to access the project relations, the portfolios and topologies that belong to a project. --- opendc-web/opendc-web-api/opendc/exts.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'opendc-web/opendc-web-api/opendc/exts.py') diff --git a/opendc-web/opendc-web-api/opendc/exts.py b/opendc-web/opendc-web-api/opendc/exts.py index 17dacd5e..3ee8babb 100644 --- a/opendc-web/opendc-web-api/opendc/exts.py +++ b/opendc-web/opendc-web-api/opendc/exts.py @@ -83,10 +83,9 @@ def requires_scope(required_scope): @wraps(f) def decorated(*args, **kwargs): if not has_scope(required_scope): - raise AuthError({ - "code": "Unauthorized", - "description": "You don't have access to this resource" - }, 403) + raise AuthError({"code": "Unauthorized", "description": "You don't have access to this resource"}, 403) return f(*args, **kwargs) + return decorated + return decorator -- cgit v1.2.3