summaryrefslogtreecommitdiff
path: root/api/main.py
diff options
context:
space:
mode:
authorjc0b <j@jc0b.computer>2020-07-21 21:22:44 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2020-08-24 19:48:14 +0200
commit67b6ec800df8e023efadb60ae5f7919030b19789 (patch)
tree9aa496408a4097857b6a032b84dd0a396321e1d3 /api/main.py
parent5b4ab37ac7be2b2c34e2fad928b0cd7f3a837263 (diff)
parent04686bf5cef4aea51fd613a158aa8b155763d0e7 (diff)
Merge branch 'master' onto local working copy
Preserve working copy while updating
Diffstat (limited to 'api/main.py')
-rwxr-xr-xapi/main.py173
1 files changed, 173 insertions, 0 deletions
diff --git a/api/main.py b/api/main.py
new file mode 100755
index 00000000..7544333a
--- /dev/null
+++ b/api/main.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python3
+import json
+import os
+import sys
+import traceback
+import urllib.request
+
+import flask_socketio
+from dotenv import load_dotenv
+from flask import Flask, request, jsonify
+from flask_compress import Compress
+from flask_cors import CORS
+from oauth2client import client, crypt
+
+from opendc.models.user import User
+from opendc.util import rest, path_parser, database
+from opendc.util.exceptions import AuthorizationTokenError, RequestInitializationError
+
+load_dotenv()
+
+TEST_MODE = "OPENDC_FLASK_TESTING" in os.environ
+
+# Set up database if not testing
+if not TEST_MODE:
+ database.DB.initialize_database(
+ user=os.environ['OPENDC_DB_USERNAME'],
+ password=os.environ['OPENDC_DB_PASSWORD'],
+ database=os.environ['OPENDC_DB'],
+ host=os.environ['OPENDC_DB_HOST'] if 'OPENDC_DB_HOST' in os.environ else 'localhost')
+
+# Set up the core app
+FLASK_CORE_APP = Flask(__name__)
+FLASK_CORE_APP.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET']
+
+# Set up CORS support for local setups
+if 'localhost' in os.environ['OPENDC_SERVER_BASE_URL']:
+ CORS(FLASK_CORE_APP)
+
+compress = Compress()
+compress.init_app(FLASK_CORE_APP)
+
+if 'OPENDC_SERVER_BASE_URL' in os.environ or 'localhost' in os.environ['OPENDC_SERVER_BASE_URL']:
+ SOCKET_IO_CORE = flask_socketio.SocketIO(FLASK_CORE_APP, cors_allowed_origins="*")
+else:
+ SOCKET_IO_CORE = flask_socketio.SocketIO(FLASK_CORE_APP)
+
+
+@FLASK_CORE_APP.route('/tokensignin', methods=['POST'])
+def sign_in():
+ """Authenticate a user with Google sign in"""
+
+ try:
+ token = request.form['idtoken']
+ except KeyError:
+ return 'No idtoken provided', 401
+
+ try:
+ idinfo = client.verify_id_token(token, os.environ['OPENDC_OAUTH_CLIENT_ID'])
+
+ if idinfo['aud'] != os.environ['OPENDC_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.')
+ except ValueError:
+ url = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={}".format(token)
+ req = urllib.request.Request(url)
+ response = urllib.request.urlopen(url=req, timeout=30)
+ res = response.read()
+ idinfo = json.loads(res)
+ except crypt.AppIdentityError as e:
+ return 'Did not successfully authenticate'
+
+ user = User.from_google_id(idinfo['sub'])
+
+ data = {'isNewUser': user.obj is None}
+
+ if user.obj is not None:
+ data['userId'] = user.get_id()
+
+ return jsonify(**data)
+
+
+@FLASK_CORE_APP.route('/api/<string:version>/<path:endpoint_path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
+def api_call(version, endpoint_path):
+ """Call an API endpoint directly over HTTP."""
+
+ # Get path and parameters
+ (path, path_parameters) = path_parser.parse(version, endpoint_path)
+
+ query_parameters = request.args.to_dict()
+ for param in query_parameters:
+ try:
+ query_parameters[param] = int(query_parameters[param])
+ except:
+ pass
+
+ try:
+ body_parameters = json.loads(request.get_data())
+ except:
+ body_parameters = {}
+
+ # Create and call request
+ (req, response) = _process_message({
+ 'id': 0,
+ 'method': request.method,
+ 'parameters': {
+ 'body': body_parameters,
+ 'path': path_parameters,
+ 'query': query_parameters
+ },
+ 'path': path,
+ 'token': request.headers.get('auth-token')
+ })
+
+ print(
+ f'HTTP:\t{req.method} to `/{req.path}` resulted in {response.status["code"]}: {response.status["description"]}')
+ sys.stdout.flush()
+
+ flask_response = jsonify(json.loads(response.to_JSON()))
+ flask_response.status_code = response.status['code']
+ return flask_response
+
+
+@SOCKET_IO_CORE.on('request')
+def receive_message(message):
+ """"Receive a SocketIO request"""
+ (req, res) = _process_message(message)
+
+ print(f'Socket: {req.method} to `/{req.path}` resulted in {res.status["code"]}: {res.status["description"]}')
+ sys.stdout.flush()
+
+ flask_socketio.emit('response', res.to_JSON(), json=True)
+
+
+def _process_message(message):
+ """Process a request message and return the response."""
+
+ try:
+ req = rest.Request(message)
+ res = req.process()
+
+ return req, res
+
+ except AuthorizationTokenError:
+ res = rest.Response(401, 'Authorization error')
+ res.id = message['id']
+
+ except RequestInitializationError as e:
+ res = rest.Response(400, str(e))
+ res.id = message['id']
+
+ if not 'method' in message:
+ message['method'] = 'UNSPECIFIED'
+ if not 'path' in message:
+ message['path'] = 'UNSPECIFIED'
+
+ except Exception:
+ res = rest.Response(500, 'Internal server error')
+ if 'id' in message:
+ res.id = message['id']
+ traceback.print_exc()
+
+ req = rest.Request()
+ req.method = message['method']
+ req.path = message['path']
+
+ return req, res
+
+
+if __name__ == '__main__':
+ print("Web server started on 8081")
+ SOCKET_IO_CORE.run(FLASK_CORE_APP, host='0.0.0.0', port=8081)