Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions tensorboard/backend/http_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 [])
]
Expand Down
14 changes: 14 additions & 0 deletions tensorboard/backend/http_util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, '<b>hello</b>', '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, '<b>hello</b>', 'text/html', csp_scripts_sha256s=None)
expected_csp = (
Expand All @@ -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, '<b>hello</b>', 'text/html', csp_scripts_sha256s=None)
Expand Down
11 changes: 4 additions & 7 deletions tensorboard/defs/vulcanize.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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",
Expand All @@ -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()]),
Expand Down Expand Up @@ -155,5 +153,4 @@ tensorboard_html_binary = rule(
outputs={
"html": "%{name}.html",
"js": "%{name}.js",
"shasum": "%{name}.html.scripts_sha256",
})
107 changes: 0 additions & 107 deletions tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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())
Expand All @@ -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));
}

Expand Down Expand Up @@ -732,108 +727,6 @@ private static ImmutableMultimap<DiagnosticType, String> 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
* <script>A</script>
* <script>B</script>
* <script src="srcful1"></script>
* <script src="srcful2"></script>
* <script>C</script>
* <script>D</script>
* <script src="srcful3"></script>
* <script>E</script>
* }
* gets compiled as {@code
* <script>A,B</script>
* <script src="srcful1"></script>
* <script src="srcful2"></script>
* <script>C,D</script>
* <script src="srcful3"></script>
* <script>E</script>
* }
*
* @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<String> computeScriptShasum(Document document)
throws FileNotFoundException, IOException {
ArrayList<String> 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");
Expand Down
2 changes: 1 addition & 1 deletion tensorboard/pip_package/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
35 changes: 2 additions & 33 deletions tensorboard/plugins/core/core_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
24 changes: 5 additions & 19 deletions tensorboard/plugins/projector/projector_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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."""
Expand Down
3 changes: 2 additions & 1 deletion tensorboard/plugins/projector/tf_projector_plugin/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
],
Expand Down