diff options
Diffstat (limited to 'opendc/util/rest.py')
| -rw-r--r-- | opendc/util/rest.py | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/opendc/util/rest.py b/opendc/util/rest.py new file mode 100644 index 00000000..a52b0082 --- /dev/null +++ b/opendc/util/rest.py @@ -0,0 +1,137 @@ +import importlib +import json +import os +import sys + +from oauth2client import client, crypt + +from opendc.util import exceptions, parameter_checker + +with open('/var/www/opendc.ewi.tudelft.nl/web-server/config/keys.json') as file: + KEYS = json.load(file) + +class Request(object): + """WebSocket message to REST request mapping.""" + + def __init__(self, message): + """"Initialize a Request from a socket message.""" + + # Get the Request parameters from the message + + try: + self.message = message + + self.id = message['id'] + + self.path = message['path'] + self.method = message['method'] + + self.params_body = message['parameters']['body'] + self.params_path = message['parameters']['path'] + self.params_query = message['parameters']['query'] + + self.token = message['token'] + + except KeyError as exception: + raise exceptions.MissingRequestParameterError(exception) + + # Parse the path and import the appropriate module + + try: + self.path = message['path'].encode('ascii', 'ignore').strip('/') + + module_base = 'opendc.api.{}.endpoint' + module_path = self.path.translate(None, '{}').replace('/', '.') + + self.module = importlib.import_module(module_base.format(module_path)) + + except UnicodeError as e: + raise exceptions.UnimplementedEndpointError('Non-ASCII path') + + except ImportError: + raise exceptions.UnimplementedEndpointError( + 'Unimplemented endpoint: {}.'.format(self.path) + ) + + # Check the method + + if not self.method in ['POST', 'GET', 'PUT', 'PATCH', 'DELETE']: + raise exceptions.UnsupportedMethodError('Non-rest method: {}'.format(self.method)) + + if not hasattr(self.module, self.method): + raise exceptions.UnsupportedMethodError('Unimplemented method at endpoint {}: {}'.format(self.path, self.method)) + + # Verify the user + + try: + self.google_id = self._verify_token(self.token) + + except crypt.AppIdentityError as e: + raise exceptions.AuthorizationTokenError(e.message) + + def _verify_token(self, token): + """Return the ID of the signed-in user. + + Or throw an Exception if the token is invalid. + """ + + try: + idinfo = client.verify_id_token(token, KEYS['OAUTH_CLIENT_ID']) + except Exception as e: + raise crypt.AppIdentityError('Exception caught trying to verify ID token: {}'.format(e)) + + if idinfo['aud'] != KEYS['OAUTH_CLIENT_ID']: + raise crypt.AppIdentityError('Unrecognized client.') + + if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: + raise crypt.AppIdentityError('Wrong issuer.') + + return idinfo['sub'] + + def check_required_parameters(self, **kwargs): + """Raise an error if a parameter is missing or of the wrong type.""" + + parameter_checker.check(self, **kwargs) + + def process(self): + """Process the Request and return a Response.""" + + method = getattr(self.module, self.method) + + response = method(self) + response.id = self.id + + return response + + def to_JSON(self): + """Return a JSON representation of this Request""" + + self.message['id'] = 0 + self.message['token'] = None + + return json.dumps(self.message) + +class Response(object): + """Response to websocket mapping""" + + def __init__(self, status_code, status_description, content=None): + """Initialize a new Response.""" + + self.status = { + 'code': status_code, + 'description': status_description + } + self.content = content + + def to_JSON(self): + """"Return a JSON representation of this Response""" + + data = { + 'id': self.id, + 'status': self.status + } + + if self.content is not None: + data['content'] = self.content + + return json.dumps(data) |
