summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-api/opendc
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web/opendc-web-api/opendc')
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py6
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py4
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py2
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py2
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py6
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py2
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py2
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py8
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py2
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py2
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py6
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py6
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py2
-rw-r--r--opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py4
-rw-r--r--opendc-web/opendc-web-api/opendc/util/auth.py253
-rw-r--r--opendc-web/opendc-web-api/opendc/util/rest.py36
16 files changed, 282 insertions, 61 deletions
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py
index 0ba61a13..c856f4ce 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py
@@ -11,7 +11,7 @@ def GET(request):
portfolio = Portfolio.from_id(request.params_path['portfolioId'])
portfolio.check_exists()
- portfolio.check_user_access(request.google_id, False)
+ portfolio.check_user_access(request.current_user['sub'], False)
return Response(200, 'Successfully retrieved portfolio.', portfolio.obj)
@@ -30,7 +30,7 @@ def PUT(request):
portfolio = Portfolio.from_id(request.params_path['portfolioId'])
portfolio.check_exists()
- portfolio.check_user_access(request.google_id, True)
+ portfolio.check_user_access(request.current_user['sub'], True)
portfolio.set_property('name',
request.params_body['portfolio']['name'])
@@ -52,7 +52,7 @@ def DELETE(request):
portfolio = Portfolio.from_id(request.params_path['portfolioId'])
portfolio.check_exists()
- portfolio.check_user_access(request.google_id, True)
+ portfolio.check_user_access(request.current_user['sub'], True)
portfolio_id = portfolio.get_id()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py
index 2f042e06..b12afce3 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py
@@ -29,13 +29,13 @@ def POST(request):
portfolio = Portfolio.from_id(request.params_path['portfolioId'])
portfolio.check_exists()
- portfolio.check_user_access(request.google_id, True)
+ portfolio.check_user_access(request.current_user['sub'], True)
scenario = Scenario(request.params_body['scenario'])
topology = Topology.from_id(scenario.obj['topology']['topologyId'])
topology.check_exists()
- topology.check_user_access(request.google_id, True)
+ topology.check_user_access(request.current_user['sub'], True)
scenario.set_property('portfolioId', portfolio.get_id())
scenario.set_property('simulation', {'state': 'QUEUED'})
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py
index 0d9ad5cd..0de50851 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py
@@ -7,7 +7,7 @@ from opendc.util.rest import Response
def GET(request):
"""Return all prefabs the user is authorized to access"""
- user = User.from_google_id(request.google_id)
+ user = User.from_google_id(request.current_user['sub'])
user.check_exists()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py
index 723a2f0d..e77c7150 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py
@@ -15,7 +15,7 @@ def POST(request):
prefab.set_property('datetimeCreated', Database.datetime_to_string(datetime.now()))
prefab.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now()))
- user = User.from_google_id(request.google_id)
+ user = User.from_google_id(request.current_user['sub'])
prefab.set_property('authorId', user.get_id())
prefab.insert()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py
index 7b81f546..f1cf1fcd 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py
@@ -12,7 +12,7 @@ def GET(request):
prefab = Prefab.from_id(request.params_path['prefabId'])
prefab.check_exists()
- prefab.check_user_access(request.google_id)
+ prefab.check_user_access(request.current_user['sub'])
return Response(200, 'Successfully retrieved prefab', prefab.obj)
@@ -25,7 +25,7 @@ def PUT(request):
prefab = Prefab.from_id(request.params_path['prefabId'])
prefab.check_exists()
- prefab.check_user_access(request.google_id)
+ prefab.check_user_access(request.current_user['sub'])
prefab.set_property('name', request.params_body['prefab']['name'])
prefab.set_property('rack', request.params_body['prefab']['rack'])
@@ -43,7 +43,7 @@ def DELETE(request):
prefab = Prefab.from_id(request.params_path['prefabId'])
prefab.check_exists()
- prefab.check_user_access(request.google_id)
+ prefab.check_user_access(request.current_user['sub'])
old_object = prefab.delete()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py
index bf031382..dacbe6a4 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py
@@ -25,7 +25,7 @@ def POST(request):
topology.set_property('projectId', project.get_id())
topology.update()
- user = User.from_google_id(request.google_id)
+ user = User.from_google_id(request.current_user['sub'])
user.obj['authorizations'].append({'projectId': project.get_id(), 'authorizationLevel': 'OWN'})
user.update()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py
index 9f6a60ec..1b229122 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py
@@ -10,7 +10,7 @@ def GET(request):
project = Project.from_id(request.params_path['projectId'])
project.check_exists()
- project.check_user_access(request.google_id, False)
+ project.check_user_access(request.current_user['sub'], False)
authorizations = project.get_all_authorizations()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py
index caac37ca..37cf1860 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py
@@ -16,7 +16,7 @@ def GET(request):
project = Project.from_id(request.params_path['projectId'])
project.check_exists()
- project.check_user_access(request.google_id, False)
+ project.check_user_access(request.current_user['sub'], False)
return Response(200, 'Successfully retrieved project', project.obj)
@@ -29,7 +29,7 @@ def PUT(request):
project = Project.from_id(request.params_path['projectId'])
project.check_exists()
- project.check_user_access(request.google_id, True)
+ project.check_user_access(request.current_user['sub'], True)
project.set_property('name', request.params_body['project']['name'])
project.set_property('datetime_last_edited', Database.datetime_to_string(datetime.now()))
@@ -46,7 +46,7 @@ def DELETE(request):
project = Project.from_id(request.params_path['projectId'])
project.check_exists()
- project.check_user_access(request.google_id, True)
+ project.check_user_access(request.current_user['sub'], True)
for topology_id in project.obj['topologyIds']:
topology = Topology.from_id(topology_id)
@@ -56,7 +56,7 @@ def DELETE(request):
portfolio = Portfolio.from_id(portfolio_id)
portfolio.delete()
- user = User.from_google_id(request.google_id)
+ user = User.from_google_id(request.current_user['sub'])
user.obj['authorizations'] = list(
filter(lambda x: x['projectId'] != project.get_id(), user.obj['authorizations']))
user.update()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py
index 2cdb1194..18b4d007 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py
@@ -20,7 +20,7 @@ def POST(request):
project = Project.from_id(request.params_path['projectId'])
project.check_exists()
- project.check_user_access(request.google_id, True)
+ project.check_user_access(request.current_user['sub'], True)
portfolio = Portfolio(request.params_body['portfolio'])
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py
index 44a0d575..47f2a207 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py
@@ -14,7 +14,7 @@ def POST(request):
project = Project.from_id(request.params_path['projectId'])
project.check_exists()
- project.check_user_access(request.google_id, True)
+ project.check_user_access(request.current_user['sub'], True)
topology = Topology({
'projectId': project.get_id(),
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py
index 88a74e9c..7399f98c 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py
@@ -11,7 +11,7 @@ def GET(request):
scenario = Scenario.from_id(request.params_path['scenarioId'])
scenario.check_exists()
- scenario.check_user_access(request.google_id, False)
+ scenario.check_user_access(request.current_user['sub'], False)
return Response(200, 'Successfully retrieved scenario.', scenario.obj)
@@ -26,7 +26,7 @@ def PUT(request):
scenario = Scenario.from_id(request.params_path['scenarioId'])
scenario.check_exists()
- scenario.check_user_access(request.google_id, True)
+ scenario.check_user_access(request.current_user['sub'], True)
scenario.set_property('name',
request.params_body['scenario']['name'])
@@ -44,7 +44,7 @@ def DELETE(request):
scenario = Scenario.from_id(request.params_path['scenarioId'])
scenario.check_exists()
- scenario.check_user_access(request.google_id, True)
+ scenario.check_user_access(request.current_user['sub'], True)
scenario_id = scenario.get_id()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py
index ea82b2e2..80618190 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py
@@ -14,7 +14,7 @@ def GET(request):
topology = Topology.from_id(request.params_path['topologyId'])
topology.check_exists()
- topology.check_user_access(request.google_id, False)
+ topology.check_user_access(request.current_user['sub'], False)
return Response(200, 'Successfully retrieved topology.', topology.obj)
@@ -25,7 +25,7 @@ def PUT(request):
topology = Topology.from_id(request.params_path['topologyId'])
topology.check_exists()
- topology.check_user_access(request.google_id, True)
+ topology.check_user_access(request.current_user['sub'], True)
topology.set_property('name', request.params_body['topology']['name'])
topology.set_property('rooms', request.params_body['topology']['rooms'])
@@ -43,7 +43,7 @@ def DELETE(request):
topology = Topology.from_id(request.params_path['topologyId'])
topology.check_exists()
- topology.check_user_access(request.google_id, True)
+ topology.check_user_access(request.current_user['sub'], True)
topology_id = topology.get_id()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py
index 0dcf2463..fe61ce25 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py
@@ -20,7 +20,7 @@ def POST(request):
request.check_required_parameters(body={'user': {'email': 'string'}})
user = User(request.params_body['user'])
- user.set_property('googleId', request.google_id)
+ user.set_property('googleId', request.current_user['sub'])
user.set_property('authorizations', [])
user.check_already_exists()
diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py
index be3462c0..26ff7717 100644
--- a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py
+++ b/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py
@@ -27,7 +27,7 @@ def PUT(request):
user = User.from_id(request.params_path['userId'])
user.check_exists()
- user.check_correct_user(request.google_id)
+ user.check_correct_user(request.current_user['sub'])
user.set_property('givenName', request.params_body['user']['givenName'])
user.set_property('familyName', request.params_body['user']['familyName'])
@@ -45,7 +45,7 @@ def DELETE(request):
user = User.from_id(request.params_path['userId'])
user.check_exists()
- user.check_correct_user(request.google_id)
+ user.check_correct_user(request.current_user['sub'])
for authorization in user.obj['authorizations']:
if authorization['authorizationLevel'] != 'OWN':
diff --git a/opendc-web/opendc-web-api/opendc/util/auth.py b/opendc-web/opendc-web-api/opendc/util/auth.py
new file mode 100644
index 00000000..810b582a
--- /dev/null
+++ b/opendc-web/opendc-web-api/opendc/util/auth.py
@@ -0,0 +1,253 @@
+# Copyright (c) 2021 AtLarge Research
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import json
+import time
+from functools import wraps
+
+import urllib3
+from flask import request, _request_ctx_stack
+from jose import jwt, JWTError
+from werkzeug.local import LocalProxy
+
+current_user = LocalProxy(lambda: getattr(_request_ctx_stack.top, 'current_user', None))
+
+
+class AuthError(Exception):
+ """
+ This error is thrown when the request failed to authorize.
+ """
+
+ def __init__(self, error, status_code):
+ Exception.__init__(self, error)
+ self.error = error
+ self.status_code = status_code
+
+
+class AuthManager:
+ """
+ This class handles the authorization of requests.
+ """
+
+ def __init__(self, alg, issuer, audience):
+ self._alg = alg
+ self._issuer = issuer
+ self._audience = audience
+
+ def require(self, f):
+ """Determines if the Access Token is valid
+ """
+
+ @wraps(f)
+ def decorated(*args, **kwargs):
+ token = _get_token()
+ try:
+ header = jwt.get_unverified_header(token)
+ except JWTError as e:
+ raise AuthError({"code": "invalid_token",
+ "description": str(e)}, 401)
+
+ alg = header.get('alg', None)
+ if alg != self._alg.algorithm:
+ raise AuthError({"code": "invalid_header",
+ "description": f"Signature algorithm of {alg} is not supported. Expected the ID token "
+ f"to be signed with {self._alg.algorithm}"}, 401)
+
+ kid = header.get('kid', None)
+ try:
+ secret_or_certificate = self._alg.get_key(key_id=kid)
+ except TokenValidationError as e:
+ raise AuthError({"code": "invalid_header",
+ "description": str(e)}, 401)
+ try:
+ payload = jwt.decode(token,
+ key=secret_or_certificate,
+ algorithms=[self._alg.algorithm],
+ audience=self._audience,
+ issuer=self._issuer)
+ _request_ctx_stack.top.current_user = payload
+ return f(*args, **kwargs)
+ except jwt.ExpiredSignatureError:
+ raise AuthError({"code": "token_expired",
+ "description": "token is expired"}, 401)
+ except jwt.JWTClaimsError:
+ raise AuthError({"code": "invalid_claims",
+ "description":
+ "incorrect claims,"
+ "please check the audience and issuer"}, 401)
+ except Exception as e:
+ print(e)
+ raise AuthError({"code": "invalid_header",
+ "description":
+ "Unable to parse authentication"
+ " token."}, 401)
+
+ return decorated
+
+
+def _get_token():
+ """
+ Obtain the Access Token from the Authorization Header
+ """
+ auth = request.headers.get("Authorization", None)
+ if not auth:
+ raise AuthError({"code": "authorization_header_missing",
+ "description":
+ "Authorization header is expected"}, 401)
+
+ parts = auth.split()
+
+ if parts[0].lower() != "bearer":
+ raise AuthError({"code": "invalid_header",
+ "description":
+ "Authorization header must start with"
+ " Bearer"}, 401)
+ if len(parts) == 1:
+ raise AuthError({"code": "invalid_header",
+ "description": "Token not found"}, 401)
+ if len(parts) > 2:
+ raise AuthError({"code": "invalid_header",
+ "description":
+ "Authorization header must be"
+ " Bearer token"}, 401)
+
+ token = parts[1]
+ return token
+
+
+class SymmetricJwtAlgorithm:
+ """Verifier for HMAC signatures, which rely on shared secrets.
+ Args:
+ shared_secret (str): The shared secret used to decode the token.
+ algorithm (str, optional): The expected signing algorithm. Defaults to "HS256".
+ """
+
+ def __init__(self, shared_secret, algorithm="HS256"):
+ self.algorithm = algorithm
+ self._shared_secret = shared_secret
+
+ # pylint: disable=W0613
+ def get_key(self, key_id=None):
+ """
+ Obtain the key for this algorithm.
+ :param key_id: The identifier of the key.
+ :return: The JWK key.
+ """
+ return self._shared_secret
+
+
+class AsymmetricJwtAlgorithm:
+ """Verifier for RSA signatures, which rely on public key certificates.
+ Args:
+ jwks_url (str): The url where the JWK set is located.
+ algorithm (str, optional): The expected signing algorithm. Defaults to "RS256".
+ """
+
+ def __init__(self, jwks_url, algorithm="RS256"):
+ self.algorithm = algorithm
+ self._fetcher = JwksFetcher(jwks_url)
+
+ def get_key(self, key_id=None):
+ """
+ Obtain the key for this algorithm.
+ :param key_id: The identifier of the key.
+ :return: The JWK key.
+ """
+ return self._fetcher.get_key(key_id)
+
+
+class TokenValidationError(Exception):
+ """
+ Error thrown when the token cannot be validated
+ """
+
+
+class JwksFetcher:
+ """Class that fetches and holds a JSON web key set.
+ This class makes use of an in-memory cache. For it to work properly, define this instance once and re-use it.
+ Args:
+ jwks_url (str): The url where the JWK set is located.
+ cache_ttl (str, optional): The lifetime of the JWK set cache in seconds. Defaults to 600 seconds.
+ """
+ CACHE_TTL = 600 # 10 min cache lifetime
+
+ def __init__(self, jwks_url, cache_ttl=CACHE_TTL):
+ self._jwks_url = jwks_url
+ self._http = urllib3.PoolManager()
+ self._cache_value = {}
+ self._cache_date = 0
+ self._cache_ttl = cache_ttl
+ self._cache_is_fresh = False
+
+ def _fetch_jwks(self, force=False):
+ """Attempts to obtain the JWK set from the cache, as long as it's still valid.
+ When not, it will perform a network request to the jwks_url to obtain a fresh result
+ and update the cache value with it.
+ Args:
+ force (bool, optional): whether to ignore the cache and force a network request or not. Defaults to False.
+ """
+ has_expired = self._cache_date + self._cache_ttl < time.time()
+
+ if not force and not has_expired:
+ # Return from cache
+ self._cache_is_fresh = False
+ return self._cache_value
+
+ # Invalidate cache and fetch fresh data
+ self._cache_value = {}
+ response = self._http.request('GET', self._jwks_url)
+
+ if response.status == 200:
+ # Update cache
+ jwks = json.loads(response.data.decode('utf-8'))
+ self._cache_value = self._parse_jwks(jwks)
+ self._cache_is_fresh = True
+ self._cache_date = time.time()
+ return self._cache_value
+
+ @staticmethod
+ def _parse_jwks(jwks):
+ """Converts a JWK string representation into a binary certificate in PEM format.
+ """
+ keys = {}
+
+ for key in jwks['keys']:
+ keys[key["kid"]] = key
+ return keys
+
+ def get_key(self, key_id):
+ """Obtains the JWK associated with the given key id.
+ Args:
+ key_id (str): The id of the key to fetch.
+ Returns:
+ the JWK associated with the given key id.
+
+ Raises:
+ TokenValidationError: when a key with that id cannot be found
+ """
+ keys = self._fetch_jwks()
+
+ if keys and key_id in keys:
+ return keys[key_id]
+
+ if not self._cache_is_fresh:
+ keys = self._fetch_jwks(force=True)
+ if keys and key_id in keys:
+ return keys[key_id]
+ raise TokenValidationError(f"RSA Public Key with ID {key_id} was not found.")
diff --git a/opendc-web/opendc-web-api/opendc/util/rest.py b/opendc-web/opendc-web-api/opendc/util/rest.py
index c9e98295..63d063b3 100644
--- a/opendc-web/opendc-web-api/opendc/util/rest.py
+++ b/opendc-web/opendc-web-api/opendc/util/rest.py
@@ -1,11 +1,9 @@
import importlib
import json
-import os
-
-from oauth2client import client, crypt
from opendc.util import exceptions, parameter_checker
from opendc.util.exceptions import ClientError
+from opendc.util.auth import current_user
class Request:
@@ -57,16 +55,7 @@ class Request:
raise exceptions.UnsupportedMethodError('Unimplemented method at endpoint {}: {}'.format(
self.path, self.method))
- # Verify the user
-
- if "OPENDC_FLASK_TESTING" in os.environ:
- self.google_id = 'test'
- return
-
- try:
- self.google_id = self._verify_token(self.token)
- except crypt.AppIdentityError as e:
- raise exceptions.AuthorizationTokenError(e)
+ self.current_user = current_user
def check_required_parameters(self, **kwargs):
"""Raise an error if a parameter is missing or of the wrong type."""
@@ -99,27 +88,6 @@ class Request:
return json.dumps(self.message)
- @staticmethod
- def _verify_token(token):
- """Return the ID of the signed-in user.
-
- Or throw an Exception if the token is invalid.
- """
-
- try:
- id_info = client.verify_id_token(token, os.environ['OPENDC_OAUTH_CLIENT_ID'])
- except Exception as e:
- print(e)
- raise crypt.AppIdentityError('Exception caught trying to verify ID token: {}'.format(e))
-
- if id_info['aud'] != os.environ['OPENDC_OAUTH_CLIENT_ID']:
- raise crypt.AppIdentityError('Unrecognized client.')
-
- if id_info['iss'] not in ['accounts.google.com', 'https://accounts.google.com']:
- raise crypt.AppIdentityError('Wrong issuer.')
-
- return id_info['sub']
-
class Response:
"""Response to websocket mapping"""