Fixes from frontend/backend url separation (#1829)

* Fixes from frontend/backend url separation

This fixes the CORS and CSP headers.

Also, in thie commit, I removed the cors_supports_credentials config parameter,
which was recently introduced.
Instead, the logic determines the need to use CORS headers if the
web_page_url is set.

 #1778
This commit is contained in:
bmccandless
2020-09-12 10:56:31 -07:00
committed by GitHub
parent 4b240920e2
commit 6a7ae8bc8e
5 changed files with 24 additions and 14 deletions
-3
View File
@@ -341,9 +341,6 @@ def handle_api_base_url(app, app_config):
if not api_base_url: if not api_base_url:
return return
if api_base_url.endswith("/"):
api_base_url = api_base_url[:-1]
sha256 = hashlib.sha256(api_base_url.encode()).hexdigest() sha256 = hashlib.sha256(api_base_url.encode()).hexdigest()
script_name = f"api_base_url-{sha256}.js" script_name = f"api_base_url-{sha256}.js"
script_path = os.path.join(app.root_path, "../common/web/templates", script_name) script_path = os.path.join(app.root_path, "../common/web/templates", script_name)
+1 -1
View File
@@ -296,7 +296,7 @@ class CliLaunchServer(Server):
"application/octet-stream", "application/octet-stream",
] ]
Compress(app) 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) CORS(app, supports_credentials=True)
+5 -3
View File
@@ -435,7 +435,6 @@ class ServerConfig(BaseConfig):
self.app__generate_cache_control_headers = dc["app"]["generate_cache_control_headers"] self.app__generate_cache_control_headers = dc["app"]["generate_cache_control_headers"]
self.app__server_timing_headers = dc["app"]["server_timing_headers"] self.app__server_timing_headers = dc["app"]["server_timing_headers"]
self.app__csp_directives = dc["app"]["csp_directives"] 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__api_base_url = dc["app"]["api_base_url"]
self.app__web_base_url = dc["app"]["web_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__flask_secret_key", (type(None), str))
self.check_attr("app__generate_cache_control_headers", bool) self.check_attr("app__generate_cache_control_headers", bool)
self.check_attr("app__server_timing_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__csp_directives", (type(None), dict))
self.check_attr("app__api_base_url", (type(None), str)) self.check_attr("app__api_base_url", (type(None), str))
self.check_attr("app__web_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): def get_api_base_url(self):
if self.app__api_base_url == "local": if self.app__api_base_url == "local":
return f"http://{self.app__host}:{self.app__port}" 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 return self.app__api_base_url
def get_web_base_url(self): def get_web_base_url(self):
@@ -765,7 +765,9 @@ class ServerConfig(BaseConfig):
return f"http://{self.app__host}:{self.app__port}" return f"http://{self.app__host}:{self.app__port}"
if self.app__web_base_url is None: if self.app__web_base_url is None:
return self.get_api_base_url() 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): class DatasetConfig(BaseConfig):
-5
View File
@@ -14,11 +14,6 @@ server:
server_timing_headers: false server_timing_headers: false
csp_directives: null 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. # 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. # In general api_base_url and web_base_url will not need to be set.
# There are two reasons to set these parameters: # There are two reasons to set these parameters:
+18 -2
View File
@@ -4,10 +4,11 @@ import sys
import os import os
import hashlib import hashlib
import base64 import base64
from urllib.parse import urlparse
from flask import json from flask import json
import logging import logging
from flask_talisman import Talisman from flask_talisman import Talisman
from flask_cors import CORS
from server.common.aws_secret_utils import handle_config_from_secret from server.common.aws_secret_utils import handle_config_from_secret
from server.common.errors import SecretKeyRetrievalError from server.common.errors import SecretKeyRetrievalError
@@ -41,6 +42,14 @@ class WSGIServer(Server):
def _before_adding_routes(app, app_config): def _before_adding_routes(app, app_config):
script_hashes = WSGIServer.get_csp_hashes(app, app_config) script_hashes = WSGIServer.get_csp_hashes(app, app_config)
server_config = app_config.server_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 # This hash should be in sync with the script within
# `client/configuration/webpack/obsoleteHTMLTemplate.html` # `client/configuration/webpack/obsoleteHTMLTemplate.html`
@@ -51,7 +60,7 @@ class WSGIServer(Server):
obsolete_browser_script_hash = ["'sha256-/rmgOi/skq9MpiZxPv6lPb1PNSN+Uf4NaUHO/IjyfwM='"] obsolete_browser_script_hash = ["'sha256-/rmgOi/skq9MpiZxPv6lPb1PNSN+Uf4NaUHO/IjyfwM='"]
csp = { csp = {
"default-src": ["'self'"], "default-src": ["'self'"],
"connect-src": ["'self'"], "connect-src": ["'self'"] + extra_connect_src,
"script-src": ["'self'", "'unsafe-eval'"] "script-src": ["'self'", "'unsafe-eval'"]
+ obsolete_browser_script_hash + script_hashes, + obsolete_browser_script_hash + script_hashes,
"style-src": ["'self'", "'unsafe-inline'"], "style-src": ["'self'", "'unsafe-inline'"],
@@ -70,6 +79,13 @@ class WSGIServer(Server):
v = [v] v = [v]
csp[k] = csp.get(k, []) + 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( Talisman(
app, force_https=server_config.app__force_https, frame_options="DENY", content_security_policy=csp, app, force_https=server_config.app__force_https, frame_options="DENY", content_security_policy=csp,
) )