diff --git a/server/app/app.py b/server/app/app.py index 72378caf..3b48d6ea 100644 --- a/server/app/app.py +++ b/server/app/app.py @@ -341,9 +341,6 @@ def handle_api_base_url(app, app_config): if not api_base_url: return - if api_base_url.endswith("/"): - api_base_url = api_base_url[:-1] - sha256 = hashlib.sha256(api_base_url.encode()).hexdigest() script_name = f"api_base_url-{sha256}.js" script_path = os.path.join(app.root_path, "../common/web/templates", script_name) diff --git a/server/cli/launch.py b/server/cli/launch.py index d2a95f2a..d46161d4 100644 --- a/server/cli/launch.py +++ b/server/cli/launch.py @@ -296,7 +296,7 @@ class CliLaunchServer(Server): "application/octet-stream", ] Compress(app) - if app_config.server_config.app__cors_supports_credentials or app_config.server_config.app__debug: + if app_config.server_config.app__debug: CORS(app, supports_credentials=True) diff --git a/server/common/app_config.py b/server/common/app_config.py index 487d70bb..3caccec5 100644 --- a/server/common/app_config.py +++ b/server/common/app_config.py @@ -435,7 +435,6 @@ class ServerConfig(BaseConfig): self.app__generate_cache_control_headers = dc["app"]["generate_cache_control_headers"] self.app__server_timing_headers = dc["app"]["server_timing_headers"] self.app__csp_directives = dc["app"]["csp_directives"] - self.app__cors_supports_credentials = dc["app"]["cors_supports_credentials"] self.app__api_base_url = dc["app"]["api_base_url"] self.app__web_base_url = dc["app"]["web_base_url"] @@ -506,7 +505,6 @@ class ServerConfig(BaseConfig): self.check_attr("app__flask_secret_key", (type(None), str)) self.check_attr("app__generate_cache_control_headers", bool) self.check_attr("app__server_timing_headers", bool) - self.check_attr("app__cors_supports_credentials", bool) self.check_attr("app__csp_directives", (type(None), dict)) self.check_attr("app__api_base_url", (type(None), str)) self.check_attr("app__web_base_url", (type(None), str)) @@ -758,6 +756,8 @@ class ServerConfig(BaseConfig): def get_api_base_url(self): if self.app__api_base_url == "local": return f"http://{self.app__host}:{self.app__port}" + if self.app__api_base_url and self.app__api_base_url.endswith("/"): + return self.app__api_base_url[:-1] return self.app__api_base_url def get_web_base_url(self): @@ -765,7 +765,9 @@ class ServerConfig(BaseConfig): return f"http://{self.app__host}:{self.app__port}" if self.app__web_base_url is None: return self.get_api_base_url() - return self.app__web_base_url + if self.app__web_base_url.endswith("/"): + return self.app__web_base_url[:-1] + return self.api__web_base_url class DatasetConfig(BaseConfig): diff --git a/server/common/default_config.py b/server/common/default_config.py index 3b58322f..16e0c27d 100644 --- a/server/common/default_config.py +++ b/server/common/default_config.py @@ -14,11 +14,6 @@ server: server_timing_headers: false csp_directives: null - # CORS: Cross Origin Resource Sharing. If true, this allow users to make - # authenticated requests. This allows cookies and credentials to be submitted - # across domains - cors_supports_credentials: false - # By default, cellxgene will serve api requests from the same base url as the webpage. # In general api_base_url and web_base_url will not need to be set. # There are two reasons to set these parameters: diff --git a/server/eb/app.py b/server/eb/app.py index 159e6dc4..7a5e834c 100644 --- a/server/eb/app.py +++ b/server/eb/app.py @@ -4,10 +4,11 @@ import sys import os import hashlib import base64 +from urllib.parse import urlparse from flask import json import logging from flask_talisman import Talisman - +from flask_cors import CORS from server.common.aws_secret_utils import handle_config_from_secret from server.common.errors import SecretKeyRetrievalError @@ -41,6 +42,14 @@ class WSGIServer(Server): def _before_adding_routes(app, app_config): script_hashes = WSGIServer.get_csp_hashes(app, app_config) server_config = app_config.server_config + + # add the api_base_url to the connect_src csp header. + extra_connect_src = [] + api_base_url = server_config.get_api_base_url() + if api_base_url: + parse_api_base_url = urlparse(api_base_url) + extra_connect_src = [f"{parse_api_base_url.scheme}://{parse_api_base_url.netloc}"] + # This hash should be in sync with the script within # `client/configuration/webpack/obsoleteHTMLTemplate.html` @@ -51,7 +60,7 @@ class WSGIServer(Server): obsolete_browser_script_hash = ["'sha256-/rmgOi/skq9MpiZxPv6lPb1PNSN+Uf4NaUHO/IjyfwM='"] csp = { "default-src": ["'self'"], - "connect-src": ["'self'"], + "connect-src": ["'self'"] + extra_connect_src, "script-src": ["'self'", "'unsafe-eval'"] + obsolete_browser_script_hash + script_hashes, "style-src": ["'self'", "'unsafe-inline'"], @@ -70,6 +79,13 @@ class WSGIServer(Server): v = [v] csp[k] = csp.get(k, []) + v + # Add the web_base_url to the CORS header + web_base_url = server_config.get_web_base_url() + if web_base_url: + web_base_url_parse = urlparse(web_base_url) + allowed_origin = f"{web_base_url_parse.scheme}://{web_base_url_parse.netloc}" + CORS(app, supports_credentials=True, origins=allowed_origin) + Talisman( app, force_https=server_config.app__force_https, frame_options="DENY", content_security_policy=csp, )