diff --git a/api/routes.py b/api/routes.py index 6ed2a7f64d3d237dee6774118b68b35f10ccceb6..4a7e8d86c209c4a91c7342ec8b4c91355f39b4f3 100644 --- a/api/routes.py +++ b/api/routes.py @@ -12,7 +12,8 @@ from main import app, limiter @limiter.limit("100 per minute") def home(): logging.debug("Front page requested.") - return render_template('mymap.html') + csrfToken = session.get('csrf') + return render_template('mymap.html', csrfToken=csrfToken) #Route for storing new hive locations @@ -83,8 +84,9 @@ def delete_location(): @roles_allowed(roles=['admin']) def admin_dashboard(): user = session.get('user') + csrfToken = session.get('csrf') logging.info(f"Admin {user['email']} logged in") - return render_template('admin.html', email=user['email']) + return render_template('admin.html', email=user['email'], csrfToken=csrfToken) # This will work as the login endpoint diff --git a/api/services/csrf.py b/api/services/csrf.py new file mode 100644 index 0000000000000000000000000000000000000000..f3bd1f24980f7d755622cf8974673d1db6695fca --- /dev/null +++ b/api/services/csrf.py @@ -0,0 +1,35 @@ +from flask import session, request, Response +from uuid import uuid4 + +def checkCsrfToken(): + """Checks that a valid csrf token exists in requests that can alter data""" + # no csrf check needed for static requests + if request.endpoint == 'static': + return None + + # check if a token exists in session + sessionToken = session.get('csrf') + + # issue new one if not + if not sessionToken: + print("issuing new token") + return issueToken() + + # no csrf check needed for get requests + if request.method == 'GET': + return None + + # get token from request + headerToken = request.headers.get('x-csrf-token') + + # check that the two tokens match + if headerToken != sessionToken: + return Response("Invalid csrf key", 403) + + + +def issueToken(): + """Generates a new CSRF token and stores it for current session""" + # issue new if not + sessionToken = str(uuid4()) + session['csrf'] = sessionToken diff --git a/api/services/datastore.py b/api/services/datastore.py index 5d212093d5e029e7dc18c69a0f975e1a286c344b..b716f02706c87a584632a402f9c869edd8b73333 100644 --- a/api/services/datastore.py +++ b/api/services/datastore.py @@ -149,11 +149,23 @@ def get_user(email): return None +# def create_test_user(email, roles, client): +# kind = 'UserAccount' +# #set name and kind for key +# key = client.key(kind) +# #Create datastore entity and set key +# datastore_entity = datastore.Entity(key) +# #Update hive data to entity +# datastore_entity.update({ +# 'email': email, +# 'roles': roles +# }) - +# #Store the info to datastore +# client.put(datastore_entity) @@ -182,6 +194,7 @@ def _get_datastore_client(default_namespace: bool = False): namespace='ns_test', credentials=None ) + return client diff --git a/config.py b/config.py deleted file mode 100644 index d027721c839cdddd8ab1da368be9b56b4242485b..0000000000000000000000000000000000000000 --- a/config.py +++ /dev/null @@ -1,20 +0,0 @@ -import os - -class Config(object): - DEBUG = False - TESTING = False - -class ProductionConfig(Config): - pass - -class DevConfig(Config): - DEBUG = True - FLASK_ENV = os.getenv("DEV") - DATASTORE_EMULATOR_HOST = os.getenv("DATASTORE_EMULATOR_HOST") - DATASTORE_EMULATOR_HOST_PATH = os.getenv("DATASTORE_EMULATOR_HOST_PATH") - -class TestingConfig(Config): - TESTING = True - FLASK_ENV = os.getenv("DEV") - DATASTORE_EMULATOR_HOST = os.getenv("DATASTORE_EMULATOR_HOST") - DATASTORE_EMULATOR_HOST_PATH = os.getenv("DATASTORE_EMULATOR_HOST_PATH") \ No newline at end of file diff --git a/main.py b/main.py index f006eeb1d05d491d5e1925da12bbab0095954a06..18983a50ee9ca306dd75a2e7c5f66e0cdc3821b3 100644 --- a/main.py +++ b/main.py @@ -13,6 +13,7 @@ from opencensus.trace.samplers import ProbabilitySampler from flask_babel import Babel from flask_limiter import Limiter from flask_limiter.util import get_remote_address +from api.services.csrf import checkCsrfToken app = Flask(__name__) @@ -56,6 +57,8 @@ def main(): sampler=ProbabilitySampler(rate=1.0) ) +app.before_request(checkCsrfToken) + from api.routes import * # app.config.from_object("config.DevConfig") diff --git a/static/data.js b/static/data.js index 08b8dc9be70400c22f7fe5bf3bea0becc2281c02..5ab0758a44e6c1bad519e3d87254fac16921ea9b 100644 --- a/static/data.js +++ b/static/data.js @@ -42,6 +42,7 @@ async function saveLocation(data) { method: 'POST', headers: { 'Content-Type': 'application/json', + 'x-csrf-token': csrfToken }, body: JSON.stringify(data), }); @@ -64,6 +65,7 @@ async function deleteLocation(data) { method: 'DELETE', headers: { 'Content-Type': 'application/json', + 'x-csrf-token': csrfToken }, body: JSON.stringify(data), }); @@ -97,6 +99,7 @@ async function editLocation(data) { method: 'PUT', headers: { 'Content-Type': 'application/json', + 'x-csrf-token': csrfToken }, body: JSON.stringify(data), }); diff --git a/templates/admin.html b/templates/admin.html index c104e7a7639bb7f52d722b6401a3acf075157b6f..b3c31408eba9490d040a51139dfb8cde0b5adbae 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -3,6 +3,7 @@ <head> <title>{{_("Admin interface")}}</title> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> + <script>var csrfToken = "{{ csrfToken }}"</script> <script type = "module" src="{{ url_for('static', filename='admin.js') }}"></script> </head> <body> diff --git a/templates/mymap.html b/templates/mymap.html index 931945e4d27998b998d1d205207660d9a64c1726..bbd185083543e29d353f45c04fe99e14409bc5c3 100644 --- a/templates/mymap.html +++ b/templates/mymap.html @@ -9,6 +9,7 @@ crossorigin="" /> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" /> + <script>var csrfToken = "{{ csrfToken }}"</script> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="