diff --git a/tensorboard/backend/http_util.py b/tensorboard/backend/http_util.py index cb16f882b8..2ac9bafb3b 100644 --- a/tensorboard/backend/http_util.py +++ b/tensorboard/backend/http_util.py @@ -183,13 +183,9 @@ def Respond(request, _validate_global_whitelist(_CSP_FONT_DOMAINS_WHITELIST) _validate_global_whitelist(_CSP_SCRIPT_DOMAINS_WHITELIST) - enable_unsafe_eval = ( - (_CSP_SCRIPT_DOMAINS_WHITELIST or csp_scripts_sha256s) - and _CSP_SCRIPT_UNSAFE_EVAL - ) frags = _CSP_SCRIPT_DOMAINS_WHITELIST + [ "'self'" if _CSP_SCRIPT_SELF else '', - "'unsafe-eval'" if enable_unsafe_eval else '', + "'unsafe-eval'" if _CSP_SCRIPT_UNSAFE_EVAL else '', ] + [ "'sha256-{}'".format(sha256) for sha256 in (csp_scripts_sha256s or []) ] diff --git a/tensorboard/backend/http_util_test.py b/tensorboard/backend/http_util_test.py index 6e38b18371..83c2df4bdf 100644 --- a/tensorboard/backend/http_util_test.py +++ b/tensorboard/backend/http_util_test.py @@ -201,6 +201,19 @@ def testCsp(self): @mock.patch.object(http_util, '_CSP_SCRIPT_SELF', False) def testCsp_noHash(self): + q = wrappers.Request(wtest.EnvironBuilder().get_environ()) + r = http_util.Respond(q, 'hello', 'text/html', csp_scripts_sha256s=None) + expected_csp = ( + "default-src 'self';font-src 'self';frame-ancestors *;" + "frame-src 'self';img-src 'self' data: blob:;object-src 'none';" + "style-src 'self' https://www.gstatic.com data: 'unsafe-inline';" + "script-src 'unsafe-eval'" + ) + self.assertEqual(r.headers.get('Content-Security-Policy'), expected_csp) + + @mock.patch.object(http_util, '_CSP_SCRIPT_SELF', False) + @mock.patch.object(http_util, '_CSP_SCRIPT_UNSAFE_EVAL', False) + def testCsp_noHash_noUnsafeEval(self): q = wrappers.Request(wtest.EnvironBuilder().get_environ()) r = http_util.Respond(q, 'hello', 'text/html', csp_scripts_sha256s=None) expected_csp = ( @@ -212,6 +225,7 @@ def testCsp_noHash(self): self.assertEqual(r.headers.get('Content-Security-Policy'), expected_csp) @mock.patch.object(http_util, '_CSP_SCRIPT_SELF', True) + @mock.patch.object(http_util, '_CSP_SCRIPT_UNSAFE_EVAL', False) def testCsp_onlySelf(self): q = wrappers.Request(wtest.EnvironBuilder().get_environ()) r = http_util.Respond(q, 'hello', 'text/html', csp_scripts_sha256s=None) diff --git a/tensorboard/defs/vulcanize.bzl b/tensorboard/defs/vulcanize.bzl index 4cd327a82b..6c3566ded8 100644 --- a/tensorboard/defs/vulcanize.bzl +++ b/tensorboard/defs/vulcanize.bzl @@ -22,10 +22,9 @@ def _tensorboard_html_binary(ctx): The rule outputs a HTML that resolves all HTML import statements into one document. When compile option is on, it compiles all script sources with - JSCompiler and combines script elements. The rule also outputs - [name].html.scripts_sha256 file that contains sha256 hash, in base64, of all - script elements (sources inside element and content of JavaScript src they - point at). The hashes are delimited by newline. + JSCompiler (unless DOM is annotated to opt-out of compilation). When js_path + is specified, the rule combines content of all script elements to a JavaScript + file. """ deps = unfurl(ctx.attr.deps, provider="webfiles") @@ -55,7 +54,7 @@ def _tensorboard_html_binary(ctx): ignore_regexs_file_set, ]).to_list(), tools=jslibs, - outputs=[ctx.outputs.html, ctx.outputs.js, ctx.outputs.shasum], + outputs=[ctx.outputs.html, ctx.outputs.js], executable=ctx.executable._Vulcanize, arguments=([ctx.attr.compilation_level, "true" if ctx.attr.compile else "false", @@ -65,7 +64,6 @@ def _tensorboard_html_binary(ctx): ctx.attr.js_path, ctx.outputs.html.path, ctx.outputs.js.path, - ctx.outputs.shasum.path, ignore_regexs_file_path] + [f.path for f in jslibs.to_list()] + [f.path for f in manifests.to_list()]), @@ -155,5 +153,4 @@ tensorboard_html_binary = rule( outputs={ "html": "%{name}.html", "js": "%{name}.js", - "shasum": "%{name}.html.scripts_sha256", }) diff --git a/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java b/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java index aeba9bfb45..c495e174ce 100644 --- a/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java +++ b/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java @@ -30,8 +30,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -import com.google.common.hash.Hashing; -import com.google.common.io.BaseEncoding; import com.google.javascript.jscomp.BasicErrorManager; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.CompilationLevel; @@ -138,7 +136,6 @@ public static void main(String[] args) Webpath jsPath = Webpath.get(args[argIdx++]); Path output = Paths.get(args[argIdx++]); Path jsOutput = Paths.get(args[argIdx++]); - Path shasumOutput = Paths.get(args[argIdx++]); if (!args[argIdx++].equals(NO_NOINLINE_FILE_PROVIDED)) { String ignoreFile = new String(Files.readAllBytes(Paths.get(args[argIdx])), UTF_8); Arrays.asList(ignoreFile.split("\n")) @@ -169,7 +166,6 @@ public static void main(String[] args) transform(document); if (wantsCompile) { compile(); - combineScriptElements(document); } else if (firstScript != null) { firstScript.before( new Element(Tag.valueOf("script"), firstScript.baseUri()) @@ -189,7 +185,6 @@ public static void main(String[] args) createFile( jsOutput, shouldExtractJs ? extractAndTransformJavaScript(document, jsPath) : ""); // Write an empty file for shasum when all scripts are extracted out. - createFile(shasumOutput, shouldExtractJs ? "" : getScriptsShasums(document)); createFile(output, Html5Printer.stringify(document)); } @@ -732,108 +727,6 @@ private static ImmutableMultimap initDiagnosticGroups() return ImmutableMultimap.copyOf(builder); } - /** - * Combine content of script tags into a group. To guarantee the correctness, it only groups - * content of `src`-less scripts between `src`-full scripts. The last combination gets inserted at - * the end of the document. - * e.g., {@code - * - * - * - * - * - * - * - * - * } - * gets compiled as {@code - * - * - * - * - * - * - * } - * - * @deprecated Script combination is deprecated in favor of script extraction. - */ - @Deprecated - private static void combineScriptElements(Document document) { - Elements scripts = document.getElementsByTag("script"); - StringBuilder sourcesBuilder = new StringBuilder(); - - for (Element script : scripts) { - if (!script.attr("src").isEmpty()) { - if (sourcesBuilder.length() == 0) { - continue; - } - - Element scriptElement = new Element(Tag.valueOf("script"), "") - .appendChild(new DataNode(sourcesBuilder.toString(), "")); - script.before(scriptElement); - sourcesBuilder = new StringBuilder(); - } else { - sourcesBuilder.append(script.html()).append("\n"); - script.remove(); - } - } - - // jsoup parser creates body elements for each HTML files. Since document.body() returns the - // first instance and we want to insert the script element at the end of the document, we - // manually grab the last one. - Element lastBody = Iterables.getLast(document.getElementsByTag("body")); - - Element scriptElement = new Element(Tag.valueOf("script"), "") - .appendChild(new DataNode(sourcesBuilder.toString(), "")); - lastBody.appendChild(scriptElement); - } - - /** @deprecated Shasum is deprecated in favor of script extraction. */ - @Deprecated - private static ArrayList computeScriptShasum(Document document) - throws FileNotFoundException, IOException { - ArrayList hashes = new ArrayList<>(); - for (Element script : document.getElementsByTag("script")) { - String src = script.attr("src"); - String sourceContent; - if (src.isEmpty()) { - sourceContent = script.html(); - } else { - // script element that remains are the ones with src that is absolute or annotated with - // `jscomp-ignore`. They must resolve from the root because those srcs are rootified. - Webpath webpathSrc = Webpath.get(src); - Webpath webpath = Webpath.get("/").resolve(Webpath.get(src)).normalize(); - if (isAbsolutePath(webpathSrc)) { - System.err.println( - "WARNING: " - + webpathSrc - + " refers to a remote resource. Please add it to CSP manually. Detail: " - + script.outerHtml()); - continue; - } else if (!webfiles.containsKey(webpath)) { - throw new FileNotFoundException( - "Expected webfiles for " + webpath + " to exist. Related: " + script.outerHtml()); - } - sourceContent = new String(Files.readAllBytes(webfiles.get(webpath)), UTF_8); - } - String hash = BaseEncoding.base64().encode( - Hashing.sha256().hashString(sourceContent, UTF_8).asBytes()); - hashes.add(hash); - } - return hashes; - } - - /** - * Writes sha256 of script tags in base64 in the document. - * - * @deprecated Shasum is deprecated in favor of script extraction. - */ - @Deprecated - private static String getScriptsShasums(Document document) - throws FileNotFoundException, IOException { - return Joiner.on("\n").join(computeScriptShasum(document)); - } - private static String extractScriptContent(Document document) throws FileNotFoundException, IOException, IllegalArgumentException { Elements scripts = document.getElementsByTag("script"); diff --git a/tensorboard/pip_package/setup.py b/tensorboard/pip_package/setup.py index f295bb5864..52c4c911f7 100644 --- a/tensorboard/pip_package/setup.py +++ b/tensorboard/pip_package/setup.py @@ -76,7 +76,7 @@ def get_readme(): 'tensorboard.plugins.projector': [ 'tf_projector_plugin/index.js', 'tf_projector_plugin/projector_binary.html', - 'tf_projector_plugin/projector_binary.html.scripts_sha256', + 'tf_projector_plugin/projector_binary.js', ], }, # Disallow python 3.0 and 3.1 which lack a 'futures' module (see above). diff --git a/tensorboard/plugins/core/core_plugin.py b/tensorboard/plugins/core/core_plugin.py index 6e290f1b53..d09c2e5e23 100644 --- a/tensorboard/plugins/core/core_plugin.py +++ b/tensorboard/plugins/core/core_plugin.py @@ -42,8 +42,6 @@ # for more details. DEFAULT_PORT = 6006 -SHASUM_DIR = '_shasums' -SHASUM_FILE_SUFFIX = '.scripts_sha256' class CorePlugin(base_plugin.TBPlugin): """Core plugin for TensorBoard. @@ -102,27 +100,9 @@ def get_resource_apps(self): with self._assets_zip_provider() as fp: with zipfile.ZipFile(fp) as zip_: for path in zip_.namelist(): - # Do not serve the shasum data as static files. - if path.startswith(SHASUM_DIR): - continue - gzipped_asset_bytes = _gzip(zip_.read(path)) - - if os.path.splitext(path)[1] == '.html': - checksum_path = os.path.join(SHASUM_DIR, path + SHASUM_FILE_SUFFIX) - # TODO(stephanwlee): devise a way to omit font-roboto/roboto.html from - # the assets zip file. - if checksum_path in zip_.namelist(): - lines = zip_.read(checksum_path).splitlines(False); - shasums = [hash.decode('utf8') for hash in lines] - else: - shasums = None - - wsgi_app = functools.partial( - self._serve_html, shasums, gzipped_asset_bytes) - else: - wsgi_app = functools.partial( - self._serve_asset, path, gzipped_asset_bytes) + wsgi_app = functools.partial( + self._serve_asset, path, gzipped_asset_bytes) apps['/' + path] = wsgi_app apps['/'] = apps['/index.html'] return apps @@ -142,17 +122,6 @@ def _serve_asset(self, path, gzipped_asset_bytes, request): return http_util.Respond( request, gzipped_asset_bytes, mimetype, content_encoding='gzip') - @wrappers.Request.application - def _serve_html(self, shasums, gzipped_asset_bytes, request): - """Serves a pre-gzipped static HTML with script shasums.""" - return http_util.Respond( - request, - gzipped_asset_bytes, - 'text/html', - content_encoding='gzip', - csp_scripts_sha256s=shasums, - ) - @wrappers.Request.application def _serve_environment(self, request): """Serve a JSON object containing some base properties used by the frontend. diff --git a/tensorboard/plugins/projector/projector_plugin.py b/tensorboard/plugins/projector/projector_plugin.py index 68fab242ee..11c36a4b64 100644 --- a/tensorboard/plugins/projector/projector_plugin.py +++ b/tensorboard/plugins/projector/projector_plugin.py @@ -269,8 +269,12 @@ def get_plugin_apps(self): os.path.join('tf_projector_plugin', 'index.js')), '/projector_binary.html': functools.partial( - self._serve_html, + self._serve_file, os.path.join('tf_projector_plugin', 'projector_binary.html')), + '/projector_binary.js': + functools.partial( + self._serve_file, + os.path.join('tf_projector_plugin', 'projector_binary.js')), } return self._handlers @@ -489,24 +493,6 @@ def _serve_file(self, file_path, request): mimetype = mimetypes.guess_type(file_path)[0] return Respond(request, read_file.read(), content_type=mimetype) - @wrappers.Request.application - def _serve_html(self, file_path, request): - """Returns a resource file.""" - res_path = os.path.join(os.path.dirname(__file__), file_path) - sha_path = '%s.scripts_sha256' % res_path - with open(sha_path, 'rb') as sha_file: - lines = sha_file.read().splitlines(False); - shasums = [hash.decode('utf8') for hash in lines] - - with open(res_path, 'rb') as read_file: - mimetype = mimetypes.guess_type(file_path)[0] - return Respond( - request, - read_file.read(), - content_type='text/html', - csp_scripts_sha256s=shasums, - ) - @wrappers.Request.application def _serve_runs(self, request): """Returns a list of runs that have embeddings.""" diff --git a/tensorboard/plugins/projector/tf_projector_plugin/BUILD b/tensorboard/plugins/projector/tf_projector_plugin/BUILD index a523416f98..73b31afb9f 100644 --- a/tensorboard/plugins/projector/tf_projector_plugin/BUILD +++ b/tensorboard/plugins/projector/tf_projector_plugin/BUILD @@ -10,7 +10,7 @@ tf_web_library( srcs = [ # Keep this in sync with pip_package/setup.py ":projector_binary.html", - ":projector_binary.html.scripts_sha256", + ":projector_binary.js", "index.js", ], path = "/tf-projector", @@ -21,6 +21,7 @@ tensorboard_html_binary( compile = True, input_path = "/tf-projector/tf-projector-plugin.html", output_path = "/tf-projector/projector_binary.html", + js_path = "/projector_binary.js", deps = [ ":tf_projector_plugin", ],