From 752b9e4ab3a9a29c573d07d249476b73be456a27 Mon Sep 17 00:00:00 2001 From: Bruce Martin Date: Tue, 24 Mar 2020 09:05:37 -0600 Subject: [PATCH] CORS and CSP headers (#1286) * do in-app compression only for CLI * CORS and CSP headers * lint * add --debug to targets * lint * fix botched merge with master --- client/Makefile | 4 ++-- server/app/app.py | 8 +++++--- server/cli/launch.py | 8 ++++++-- server/common/app_config.py | 1 + server/common/default_config.py | 1 + server/eb/app.py | 15 +++++++++++++-- server/requirements.txt | 1 + 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/client/Makefile b/client/Makefile index 1f658fcf..e3aa9d07 100644 --- a/client/Makefile +++ b/client/Makefile @@ -42,11 +42,11 @@ start-server: .PHONY: backend-dev backend-dev: server-requirements - source ../venv/bin/activate && $(MAKE) start-server + source ../venv/bin/activate && CXG_OPTIONS='--debug' $(MAKE) start-server .PHONY: backend-dev-anno-ontology backend-dev-anno-ontology: server-requirements - CXG_OPTIONS='--experimental-annotations-ontology' \ + CXG_OPTIONS='--experimental-annotations-ontology --debug' \ $(MAKE) backend-dev .PHONY: test diff --git a/server/app/app.py b/server/app/app.py index 639a6b2a..dc2bfe1c 100644 --- a/server/app/app.py +++ b/server/app/app.py @@ -4,7 +4,6 @@ import logging from flask import Flask, redirect, current_app, make_response, render_template, abort from flask import Blueprint, request, send_from_directory -from flask_cors import CORS from flask_restful import Api, Resource from http import HTTPStatus @@ -204,10 +203,9 @@ class Server: def __init__(self, matrix_data_cache_manager, annotations, app_config): self.app = Flask(__name__, static_folder="../common/web/static") + self._before_adding_routes(app_config) self.app.json_encoder = Float32JSONEncoder - CORS(self.app, supports_credentials=True) - # enable session data self.app.permanent_session_lifetime = datetime.timedelta(days=50 * 365) @@ -238,3 +236,7 @@ class Server: self.app.matrix_data_cache_manager = matrix_data_cache_manager self.app.annotations = annotations self.app.app_config = app_config + + def _before_adding_routes(self, app_config): + """ will be called before routes are added. Subclass protocol """ + pass diff --git a/server/cli/launch.py b/server/cli/launch.py index 06523ee8..7b4644cc 100644 --- a/server/cli/launch.py +++ b/server/cli/launch.py @@ -7,6 +7,7 @@ import webbrowser import click from flask_compress import Compress +from flask_cors import CORS from server.common.utils import sort_options from server.common.errors import DatasetAccessError, ConfigurationError @@ -276,6 +277,8 @@ class CliLaunchServer(Server): """ def __init__(self, matrix_data_cache_manager, annotations, app_config): super().__init__(matrix_data_cache_manager, annotations, app_config) + + def _before_adding_routes(self, app_config): self.app.config["COMPRESS_MIMETYPES"] = [ "text/html", "text/css", @@ -284,8 +287,9 @@ class CliLaunchServer(Server): "application/javascript", "application/octet-stream", ] - compress = Compress(self.app) - compress.init_app(self.app) + Compress(self.app) + if app_config.server__debug: + CORS(self.app, supports_credentials=True) @sort_options diff --git a/server/common/app_config.py b/server/common/app_config.py index b908685e..f7b57ccf 100644 --- a/server/common/app_config.py +++ b/server/common/app_config.py @@ -52,6 +52,7 @@ class AppConfig(object): self.server__open_browser = dc["server"]["open_browser"] self.server__about_legal_tos = dc["server"]["about_legal_tos"] self.server__about_legal_privacy = dc["server"]["about_legal_privacy"] + self.server__force_https = dc["server"]["force_https"] self.multi_dataset__dataroot = dc["multi_dataset"]["dataroot"] self.multi_dataset__index = dc["multi_dataset"]["index"] self.multi_dataset__allowed_matrix_types = dc["multi_dataset"]["allowed_matrix_types"] diff --git a/server/common/default_config.py b/server/common/default_config.py index d6801c31..4b6618b3 100644 --- a/server/common/default_config.py +++ b/server/common/default_config.py @@ -12,6 +12,7 @@ server: open_browser: false about_legal_tos: null about_legal_privacy: null + force_https: false presentation: max_categories: 1000 diff --git a/server/eb/app.py b/server/eb/app.py index 5e80b158..c7043787 100644 --- a/server/eb/app.py +++ b/server/eb/app.py @@ -3,6 +3,7 @@ import sys import os import logging +from flask_talisman import Talisman if os.path.isdir("/opt/python/log"): # This is the standard location where Amazon EC2 instances store the application logs. @@ -27,6 +28,16 @@ except Exception: logging.critical("Exception importing server modules", exc_info=True) sys.exit(1) + +class WSGIServer(Server): + def __init__(self, matrix_data_cache_manager, annotations, app_config): + super().__init__(matrix_data_cache_manager, annotations, app_config) + + def _before_adding_routes(self, app_config): + csp = {"default-src": "'self' 'unsafe-inline' 'unsafe-eval'", "img-src": ["'self'", "data:"]} + Talisman(self.app, force_https=app_config.server__force_https, content_security_policy=csp) + + try: dataroot = os.getenv("CXG_DATAROOT") app_config = AppConfig() @@ -38,7 +49,7 @@ try: if dataroot: logging.info(f"Configuration from CXG_DATAROOT") - app_config.update(multi_dataset__dataroot=dataroot,) + app_config.update(multi_dataset__dataroot=dataroot) # features are unsupported in the current hosted server app_config.update( @@ -52,7 +63,7 @@ try: app_config.complete_config(matrix_data_cache_manager, logging.info) user_annotations = app_config.user_annotations - server = Server(matrix_data_cache_manager, user_annotations, app_config) + server = WSGIServer(matrix_data_cache_manager, user_annotations, app_config) debug = False application = server.app diff --git a/server/requirements.txt b/server/requirements.txt index a9713b22..82d53191 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -6,6 +6,7 @@ Flask-Compress>=1.4.0 Flask-Cors>=3.0.6 Flask-RESTful>=0.3.6 flask-server-timing>=0.1.2 +flask-talisman>=0.7.0 flatbuffers>=1.10.0 flatten-dict>=0.2.0 fsspec>=0.4.4