diff --git a/.gitignore b/.gitignore index c5a4a7aa..9c2e4450 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,14 @@ server/common/web/static/img/ server/common/web/static/media/ server/common/web/static/fonts/ server/common/web/static/js/ -server/common/web/templates/index\.html +server/common/web/templates/ server/common/web/csp-hashes.json +# eb build +artifact.dir +artifact.zip +customize + # Jupyter Notebook .ipynb_checkpoints *.ipynb diff --git a/client/Makefile b/client/Makefile index 256048f4..ff8eb1b5 100644 --- a/client/Makefile +++ b/client/Makefile @@ -1,8 +1,4 @@ include ../common.mk -# https://stackoverflow.com/a/14777895/9587410 -ifeq ($(shell uname),Darwin) # is Windows_NT on XP, 2000, 7, Vista, 10... - IS_DARWIN := "true" -endif ANNOTATIONS := $(if $(ANNOTATIONS),$(ANNOTATIONS),../server/test/test_datasets/pbmc3k-annotations.csv) ANNOTATIONS_FILENAME := $(shell basename $(ANNOTATIONS)) @@ -21,8 +17,9 @@ install: npm install .PHONY: build +WEBPACK_CONFIG ?= configuration/webpack/webpack.config.prod.js build: - npm run build + npm run build $(WEBPACK_CONFIG) # Formatting code diff --git a/client/configuration/webpack/cspHashPlugin.js b/client/configuration/webpack/cspHashPlugin.js index fa532ef6..d56d2b32 100644 --- a/client/configuration/webpack/cspHashPlugin.js +++ b/client/configuration/webpack/cspHashPlugin.js @@ -32,17 +32,15 @@ class CspHashPlugin { }; } - // remove no-csp-hash attributes - let foundOne = false; - $("script[no-csp-hash]").each((i, elmt) => { - $(elmt).removeAttr("no-csp-hash"); - foundOne = true; - }); - $("style[no-csp-hash]").each((i, elmt) => { - $(elmt).removeAttr("no-csp-hash"); - foundOne = true; - }); - if (foundOne) data.html = $.html(); + // Remove no-csp-hash attributes. Cheerio does not parse Jinja templates + // correctly, so we brute force this with a regular expression. + data.html = data.html + .replace(/( - {% endfor %} + {% for script in SCRIPTS -%} + + {%- endfor %} {% for ils in INLINE_SCRIPTS %} - {% endfor %} diff --git a/client/package-lock.json b/client/package-lock.json index a40a6f23..e26d8b33 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -2547,6 +2547,49 @@ } } }, + "@sentry/cli": { + "version": "1.52.3", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.52.3.tgz", + "integrity": "sha512-QOSIg5hxAEa6v6H7oEeF6A/Rpa0wloMhbu0Qed6zHv3lyoqf0Z34Kq2jCXdqGsOE3IzkO+3CNy81F6361j5TKg==", + "dev": true, + "requires": { + "https-proxy-agent": "^5.0.0", + "mkdirp": "^0.5.5", + "node-fetch": "^2.6.0", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "@sentry/webpack-plugin": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.11.1.tgz", + "integrity": "sha512-7uQiqHXK84GYy2mL/c+cW/QBtr/D8fNmf8U5nUDN4+44aNrQxSJ4vJmYJbxYg1zO6qN/xZyqJt3uKBlgA3FkZg==", + "dev": true, + "requires": { + "@sentry/cli": "^1.52.3" + } + }, "@sinonjs/commons": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", diff --git a/client/package.json b/client/package.json index 949ed439..449a136a 100644 --- a/client/package.json +++ b/client/package.json @@ -6,8 +6,9 @@ "repository": "https://github.com/chanzuckerberg/cellxgene", "scripts": { "clean": "rimraf build", - "build": "npm run clean && webpack --config configuration/webpack/webpack.config.prod.js", - "dev": "npm run clean && webpack --config configuration/webpack/webpack.config.dev.js", + "build": "npm run clean && webpack --config", + "prod": "npm run build -- configuration/webpack/webpack.config.prod.js", + "dev": "npm run build -- configuration/webpack/webpack.config.dev.js", "fmt": "eslint --fix src", "lint": "eslint src" }, @@ -62,6 +63,7 @@ "@babel/preset-react": "^7.9.4", "@babel/register": "^7.9.0", "@babel/runtime": "^7.9.2", + "@sentry/webpack-plugin": "^1.11.1", "babel-eslint": "^10.1.0", "babel-jest": "^25.2.6", "babel-loader": "^8.1.0", diff --git a/common.mk b/common.mk index 34bc380e..dc1d3bd2 100644 --- a/common.mk +++ b/common.mk @@ -10,6 +10,12 @@ define get_or_else_dev_env_default $(if $($(1)),$($(1)),$(shell VAR=$$(sed -n 's/$(1)=\(.*\)/\1/p' $(PROJECT_ROOT)/environment.default); eval "echo \"$$VAR\"")) endef +# https://stackoverflow.com/a/14777895/9587410 +ifeq ($(shell uname),Darwin) # is Windows_NT on XP, 2000, 7, Vista, 10... + IS_DARWIN := "true" +endif + +export CELLXGENE_COMMIT := $(shell git rev-parse --short HEAD) export CXG_SERVER_PORT := $(call get_or_else_dev_env_default,CXG_SERVER_PORT) export CXG_CLIENT_PORT := $(call get_or_else_dev_env_default,CXG_CLIENT_PORT) export JEST_ENV := $(call get_or_else_dev_env_default,JEST_ENV) diff --git a/server/common/app_config.py b/server/common/app_config.py index 7674408c..b228fee2 100644 --- a/server/common/app_config.py +++ b/server/common/app_config.py @@ -287,6 +287,17 @@ class AppConfig(object): elif not isinstance(v, str): raise ConfigurationError(f"CSP directive value must be a string or list of strings.") + # scripts can be string (filename) or dict (attributes). Convert string to dict. + scripts = [] + for s in self.server__scripts: + if isinstance(s, str): + scripts.append({"src": s}) + elif isinstance(s, dict) and isinstance(s["src"], str): + scripts.append(s) + else: + raise ConfigurationError("Scripts must be string or dict") + self.server__scripts = scripts + def handle_data_locator(self, context): self.__check_attr("data_locator__s3__region_name", (type(None), bool, str)) if self.data_locator__s3__region_name is True: diff --git a/server/common/default_config.py b/server/common/default_config.py index 7c3dba4a..b4d7a989 100644 --- a/server/common/default_config.py +++ b/server/common/default_config.py @@ -8,8 +8,13 @@ server: debug: false host: "127.0.0.1" port : null - scripts : [] + + # Scripts can be a list of either file names (string) or dicts containing keys src, integrity and crossorigin. + # these will be injected into the index template as script tags with these attributes set. + scripts: [] + # Inline scripts are a list of file names, where the contents of the file will be injected into the index. inline_scripts: [] + open_browser: false about_legal_tos: null about_legal_privacy: null diff --git a/server/eb/Makefile b/server/eb/Makefile index 7d87dcb5..5e5a8a66 100644 --- a/server/eb/Makefile +++ b/server/eb/Makefile @@ -28,7 +28,7 @@ build: clean if [ -f customize/requirements.txt ] ; then \ pip install requirements-parser ; \ pip install packaging ; \ - python check_requirements.py ../requirements.txt customize/requirements.txt; \ + python3 check_requirements.py ../requirements.txt customize/requirements.txt; \ cp customize/requirements.txt artifact.dir; \ fi ; \ if [ -d customize/deploy ] ; then \ @@ -36,6 +36,7 @@ build: clean fi; \ if [ -d customize/inline_scripts ] ; then \ cp -r customize/inline_scripts/* artifact.dir/server/common/web/templates; \ + find artifact.dir/server/common/web/templates/ -type f -exec sed -i$(if $(IS_DARWIN), '',) 's/__CELLXGENE_COMMIT__/$(CELLXGENE_COMMIT)/g' {} \;; \ fi; \ if [ -d customize/ebextensions ] ; then \ cp -r customize/ebextensions/* artifact.dir/.ebextensions; \ @@ -43,6 +44,7 @@ build: clean fi; \ if [ -d customize/plugins ] ; then \ cp -r customize/plugins artifact.dir/server/; \ + find artifact.dir/server/plugins/ -type f -exec sed -i$(if $(IS_DARWIN), '',) 's/__CELLXGENE_COMMIT__/$(CELLXGENE_COMMIT)/g' {} \;; \ fi; \ (cd artifact.dir; \ cp -r server/common/web/static static; \ diff --git a/server/eb/app.py b/server/eb/app.py index 293c1e0f..c493a9b9 100644 --- a/server/eb/app.py +++ b/server/eb/app.py @@ -60,6 +60,7 @@ class WSGIServer(Server): script_hashes, style_hashes = WSGIServer.get_csp_hashes(app, app_config) csp = { "default-src": ["'self'"], + "connect-src": ["'self'"], "script-src": ["'self'", "'unsafe-eval'", "'unsafe-inline'"] + script_hashes, "style-src": ["'self'", "'unsafe-inline'"] + style_hashes, "img-src": ["'self'", "data:"],