Merge branch 'bp-improvements' into devel

This commit is contained in:
Emily Stark
2014-01-15 13:01:10 -08:00
3 changed files with 88 additions and 21 deletions

View File

@@ -111,7 +111,7 @@ Disallows inline CSS.
Finally, you can configure a whitelist of allowed requests that various types of
content can make. The following functions are defined for the content types
script, object, image, media, font, and connect.
script, object, image, media, font, frame, and connect.
<dl class="callbacks">
{{#dtdd "BrowserPolicy.content.allow&lt;ContentType&gt;Origin(origin)"}}
@@ -119,8 +119,11 @@ Allows this type of content to be loaded from the given origin. `origin` is a
string and can include an optional scheme (such as `http` or `https`), an
optional wildcard at the beginning, and an optional port which can be a
wildcard. Examples include `example.com`, `https://*.example.com`, and
`example.com:*`. You can call these functions multiple times with different
origins to specify a whitelist of allowed origins.
`example.com:*`. You can call these functions multiple times with
different origins to specify a whitelist of allowed origins. Origins
that don't specify a protocol will allow content over both HTTP and
HTTPS: passing `example.com` will allow content from both
`http://example.com` and `https://example.com`.
{{/dtdd}}
{{#dtdd "BrowserPolicy.content.allow&lt;ContentType&gt;DataUrl()"}}
@@ -159,6 +162,12 @@ allows images to have their `src` attributes point to images served from
`https://example.com`.
* `BrowserPolicy.content.allowConnectOrigin("https://example.com")` allows XMLHttpRequest
and WebSocket connections to `https://example.com`.
* `BrowserPolicy.content.allowFrameOrigin("https://example.com")` allows
your site to load the origin `https://example.com` in a frame or
iframe. The `BrowserPolicy.framing` API allows you to control which
sites can frame your site, while
`BrowserPolicy.content.allowFrameOrigin` allows you to control which
sites can be loaded inside frames on your site.
{{/better_markdown}}

View File

@@ -37,10 +37,12 @@ var cspSrcs;
var cachedCsp; // Avoid constructing the header out of cspSrcs when possible.
// CSP keywords have to be single-quoted.
var unsafeInline = "'unsafe-inline'";
var unsafeEval = "'unsafe-eval'";
var selfKeyword = "'self'";
var noneKeyword = "'none'";
var keywords = {
unsafeInline: "'unsafe-inline'",
unsafeEval: "'unsafe-eval'",
self: "'self'",
none: "'none'"
};
BrowserPolicy.content = {};
@@ -52,7 +54,7 @@ var parseCsp = function (csp) {
policy = policy.substring(0, policy.length - 1);
var srcs = policy.split(" ");
var directive = srcs[0];
if (_.indexOf(srcs, noneKeyword) !== -1)
if (_.indexOf(srcs, keywords.none) !== -1)
cspSrcs[directive] = null;
else
cspSrcs[directive] = srcs.slice(1);
@@ -81,6 +83,38 @@ var prepareForCspDirective = function (directive) {
cspSrcs[directive] = _.clone(cspSrcs["default-src"]);
};
// Add `src` to the list of allowed sources for `directive`, with the
// following modifications if `src` is an origin:
// - If `src` does not have a protocol specified, then add both
// http://<src> and https://<src>. This is to mask differing
// cross-browser behavior; some browsers interpret an origin without a
// protocol as http://<src> and some interpret it as both http://<src>
// and https://<src>
// - Trim trailing slashes from `src`, since some browsers interpret
// "foo.com/" as "foo.com" and some don't.
var addSourceForDirective = function (directive, src) {
if (_.contains(_.values(keywords), src)) {
cspSrcs[directive].push(src);
} else {
src = src.toLowerCase();
// Trim trailing slashes.
src = src.replace(/\/+$/, '');
var toAdd = [];
// If there is no protocol, add both http:// and https://.
if (! /^([a-z0-9.+-]+:)/.test(src)) {
toAdd.push("http://" + src);
toAdd.push("https://" + src);
} else {
toAdd.push(src);
}
_.each(toAdd, function (s) {
cspSrcs[directive].push(s);
});
}
};
var setDefaultPolicy = function () {
// By default, unsafe inline scripts and styles are allowed, since we expect
// many apps will use them for analytics, etc. Unsafe eval is disallowed, and
@@ -111,7 +145,7 @@ _.extend(BrowserPolicy.content, {
var header = _.map(cspSrcs, function (srcs, directive) {
srcs = srcs || [];
if (_.isEmpty(srcs))
srcs = [noneKeyword];
srcs = [keywords.none];
var directiveCsp = _.uniq(srcs).join(" ");
return directive + " " + directiveCsp + ";";
});
@@ -129,7 +163,7 @@ _.extend(BrowserPolicy.content, {
cachedCsp = null;
parseCsp(csp);
setWebAppInlineScripts(
BrowserPolicy.content._keywordAllowed("script-src", unsafeInline)
BrowserPolicy.content._keywordAllowed("script-src", keywords.unsafeInline)
);
},
@@ -142,34 +176,34 @@ _.extend(BrowserPolicy.content, {
allowInlineScripts: function () {
prepareForCspDirective("script-src");
cspSrcs["script-src"].push(unsafeInline);
cspSrcs["script-src"].push(keywords.unsafeInline);
setWebAppInlineScripts(true);
},
disallowInlineScripts: function () {
prepareForCspDirective("script-src");
removeCspSrc("script-src", unsafeInline);
removeCspSrc("script-src", keywords.unsafeInline);
setWebAppInlineScripts(false);
},
allowEval: function () {
prepareForCspDirective("script-src");
cspSrcs["script-src"].push(unsafeEval);
cspSrcs["script-src"].push(keywords.unsafeEval);
},
disallowEval: function () {
prepareForCspDirective("script-src");
removeCspSrc("script-src", unsafeEval);
removeCspSrc("script-src", keywords.unsafeEval);
},
allowInlineStyles: function () {
prepareForCspDirective("style-src");
cspSrcs["style-src"].push(unsafeInline);
cspSrcs["style-src"].push(keywords.unsafeInline);
},
disallowInlineStyles: function () {
prepareForCspDirective("style-src");
removeCspSrc("style-src", unsafeInline);
removeCspSrc("style-src", keywords.unsafeInline);
},
// Functions for setting defaults
allowSameOriginForAll: function () {
BrowserPolicy.content.allowOriginForAll(selfKeyword);
BrowserPolicy.content.allowOriginForAll(keywords.self);
},
allowDataUrlForAll: function () {
BrowserPolicy.content.allowOriginForAll("data:");
@@ -177,7 +211,7 @@ _.extend(BrowserPolicy.content, {
allowOriginForAll: function (origin) {
prepareForCspDirective("default-src");
_.each(_.keys(cspSrcs), function (directive) {
cspSrcs[directive].push(origin);
addSourceForDirective(directive, origin);
});
},
disallowAll: function () {
@@ -192,7 +226,7 @@ _.extend(BrowserPolicy.content, {
// allow<Resource>Origin, allow<Resource>Data, allow<Resource>self, and
// disallow<Resource> methods for each type of resource.
_.each(["script", "object", "img", "media",
"font", "connect", "style"],
"font", "connect", "style", "frame"],
function (resource) {
var directive = resource + "-src";
var methodResource;
@@ -214,7 +248,7 @@ _.each(["script", "object", "img", "media",
BrowserPolicy.content[allowMethodName] = function (src) {
prepareForCspDirective(directive);
cspSrcs[directive].push(src);
addSourceForDirective(directive, src);
};
if (resource === "script") {
BrowserPolicy.content[disallowMethodName] = function () {
@@ -230,7 +264,7 @@ _.each(["script", "object", "img", "media",
};
BrowserPolicy.content[allowSelfMethodName] = function () {
prepareForCspDirective(directive);
cspSrcs[directive].push(selfKeyword);
cspSrcs[directive].push(keywords.self);
};
});

View File

@@ -112,6 +112,30 @@ Tinytest.add("browser-policy - csp", function (test) {
BrowserPolicy.content.disallowObject();
test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(),
"default-src 'self'; object-src 'none';"));
// Allow foo.com; it should allow both http://foo.com and
// https://foo.com.
BrowserPolicy.content.allowImageOrigin("foo.com");
test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(),
"default-src 'self'; object-src 'none'; " +
"img-src 'self' http://foo.com https://foo.com;"));
// "Disallow all <object>" followed by "allow foo.com for all" results
// in <object> srcs from foo.com.
BrowserPolicy.content.allowOriginForAll("foo.com");
test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(),
"default-src 'self' http://foo.com https://foo.com; " +
"object-src http://foo.com https://foo.com; " +
"img-src 'self' http://foo.com https://foo.com;"));
// Check that trailing slashes are trimmed from origins.
BrowserPolicy.content.disallowAll();
BrowserPolicy.content.allowFrameOrigin("https://foo.com/");
test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(),
"default-src 'none'; frame-src https://foo.com;"));
BrowserPolicy.content.allowObjectOrigin("foo.com//");
test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(),
"default-src 'none'; frame-src https://foo.com; " +
"object-src http://foo.com https://foo.com;"));
});
Tinytest.add("browser-policy - x-frame-options", function (test) {