Commit 11357364 authored by arthursonzogni's avatar arthursonzogni Committed by Commit Bot

Align {network,content}::ContentSecurityPolicy

This CL:
1) Align the two ContentSecurityPolicy struct in between content and
   network::mojom
2) Add a content::ContentSecurityPolicy constructor from a
   network::mojom::ContentSecurityPolicy
3) Add several TODO for the frame-ancestor CSP checked out-of-blink.

This allows the future follow-ups:
  1) Replace content CSP by network::mojom CSP for every mojo IPC.
  2) Remove (gradually) every content CSP by the network::mojom ones.

Change-Id: I1b1e0658f77ce3141d263d7de09272d225a4f756
Bug: 1021462
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1926141
Auto-Submit: Arthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: default avatarLucas Gadani <lfg@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#719964}
parent 00a9d6a6
...@@ -93,6 +93,42 @@ bool HeadersContainFrameAncestorsCSP(const net::HttpResponseHeaders* headers) { ...@@ -93,6 +93,42 @@ bool HeadersContainFrameAncestorsCSP(const net::HttpResponseHeaders* headers) {
return false; return false;
} }
class FrameAncestorCSPContext : public CSPContext {
public:
explicit FrameAncestorCSPContext(
RenderFrameHostImpl* navigated_frame,
const std::vector<ContentSecurityPolicy>& policies)
: navigated_frame_(navigated_frame) {
// TODO(arthursonzogni): Refactor CSPContext to its original state, it
// shouldn't own any ContentSecurityPolicies on its own. This should be
// defined by the implementation instead. Copies could be avoided here.
for (const auto& policy : policies)
AddContentSecurityPolicy(policy);
}
private:
void ReportContentSecurityPolicyViolation(
const CSPViolationParams& violation_params) override {
return navigated_frame_->ReportContentSecurityPolicyViolation(
violation_params);
}
bool SchemeShouldBypassCSP(const base::StringPiece& scheme) override {
return navigated_frame_->SchemeShouldBypassCSP(scheme);
}
void SanitizeDataForUseInCspViolation(
bool is_redirect,
CSPDirective::Name directive,
GURL* blocked_url,
SourceLocation* source_location) const override {
return navigated_frame_->SanitizeDataForUseInCspViolation(
is_redirect, directive, blocked_url, source_location);
}
RenderFrameHostImpl* navigated_frame_;
};
} // namespace } // namespace
// static // static
...@@ -151,57 +187,40 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl( ...@@ -151,57 +187,40 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl(
network::features::kOutOfBlinkFrameAncestors)) { network::features::kOutOfBlinkFrameAncestors)) {
if (network::mojom::ContentSecurityPolicyPtr policy = if (network::mojom::ContentSecurityPolicyPtr policy =
request->response()->head.content_security_policy) { request->response()->head.content_security_policy) {
if (auto& frame_ancestors = policy->frame_ancestors) { // TODO(arthursonzogni): Remove content::ContentSecurityPolicy in favor of
// TODO(lfg, arthursonzogni): Move the frame-ancestors check to a common // network::mojom::ContentSecurityPolicy, this will avoid conversion
// ContentSecurityPolicy object instead of checking directly against the // between type here.
// CSPSourceList. // TODO(lfg): Pass every ContentSecurityPolicy here instead of one.
CSPSourceList frame_ancestors_list(*frame_ancestors); std::vector<ContentSecurityPolicy> policies = {
frame_ancestors_list.allow_response_redirects = true; ContentSecurityPolicy(std::move(policy)),
FrameTreeNode* parent = request->frame_tree_node()->parent(); };
bool has_followed_redirect = navigation_handle()->WasServerRedirect(); // TODO(lfg): If the initiating document is known and correspond to the
// Since the navigation hasn't committed yet, we need to create a // navigating frame's current document, consider using:
// CSPContext for the navigation handle. // navigation_request().common_params().source_location here instead.
CSPContext csp_context; SourceLocation empty_source_location;
csp_context.SetSelf(url::Origin::Create(navigation_handle()->GetURL()));
while (parent) { // CSP frame-ancestors are checked against the URL of every parent and are
if (CSPSourceList::Allow(frame_ancestors_list, // reported to the navigating frame.
parent->current_frame_host() FrameAncestorCSPContext csp_context(
->GetLastCommittedOrigin() NavigationRequest::From(navigation_handle())->GetRenderFrameHost(),
.GetURL(), policies);
&csp_context, has_followed_redirect, csp_context.SetSelf(url::Origin::Create(navigation_handle()->GetURL()));
is_response_check)) {
parent = parent->parent(); // Check CSP frame-ancestor against every parent.
continue; RenderFrameHostImpl* parent = request->GetParentFrame();
} while (parent) {
auto* frame_to_commit = static_cast<RenderFrameHostImpl*>( if (!csp_context.IsAllowedByCsp(
navigation_handle()->GetRenderFrameHost()); CSPDirective::FrameAncestors,
GURL blocked_url = navigation_handle()->GetURL(); parent->GetLastCommittedOrigin().GetURL(),
SourceLocation source_location; navigation_handle()->WasServerRedirect(),
frame_to_commit->SanitizeDataForUseInCspViolation( true /* is_response_check */, empty_source_location,
has_followed_redirect, CSPDirective::FrameAncestors, &blocked_url, CSPContext::CheckCSPDisposition::CHECK_ALL_CSP,
&source_location); navigation_handle()->IsFormSubmission())) {
std::vector<std::string> report_endpoints;
for (auto& url : policy->report_endpoints)
report_endpoints.push_back(url.spec());
frame_to_commit->ReportContentSecurityPolicyViolation(
// The browser doesn't have the raw CSP text to report in the
// message.
CSPViolationParams(
"frame-ancestors", "frame-ancestors",
base::StringPrintf(
"Refused to display '%s' in a frame because an ancestor "
"violates the frame-ancestors Content Security Policy.",
blocked_url.spec().c_str()),
blocked_url, report_endpoints, policy->use_reporting_api,
"" /* header */,
network::mojom::ContentSecurityPolicyType::kEnforce,
has_followed_redirect, source_location));
return NavigationThrottle::BLOCK_RESPONSE; return NavigationThrottle::BLOCK_RESPONSE;
} }
return NavigationThrottle::PROCEED; parent = parent->GetParent();
} }
return NavigationThrottle::PROCEED;
} }
} }
......
...@@ -53,6 +53,33 @@ std::string ElideURLForReportViolation(const GURL& url) { ...@@ -53,6 +53,33 @@ std::string ElideURLForReportViolation(const GURL& url) {
return url.spec(); return url.spec();
} }
// Return the error message specific to one CSP |directive|.
// $1: Blocked URL.
// $2: Blocking policy.
const char* ErrorMessage(CSPDirective::Name directive) {
switch (directive) {
case CSPDirective::FormAction:
return "Refused to send form data to '$1' because it violates the "
"following Content Security Policy directive: \"$2\".";
case CSPDirective::FrameAncestors:
return "Refused to frame '$1' because an ancestor violates the following "
"Content Security Policy directive: \"$2\".";
case CSPDirective::FrameSrc:
return "Refused to frame '$1' because it violates the "
"following Content Security Policy directive: \"$2\".";
case CSPDirective::NavigateTo:
return "Refused to navigate to '$1' because it violates the "
"following Content Security Policy directive: \"$2\".";
case CSPDirective::ChildSrc:
case CSPDirective::DefaultSrc:
case CSPDirective::Unknown:
case CSPDirective::UpgradeInsecureRequests:
NOTREACHED();
return nullptr;
};
}
void ReportViolation(CSPContext* context, void ReportViolation(CSPContext* context,
const ContentSecurityPolicy& policy, const ContentSecurityPolicy& policy,
const CSPDirective& directive, const CSPDirective& directive,
...@@ -60,49 +87,39 @@ void ReportViolation(CSPContext* context, ...@@ -60,49 +87,39 @@ void ReportViolation(CSPContext* context,
const GURL& url, const GURL& url,
bool has_followed_redirect, bool has_followed_redirect,
const SourceLocation& source_location) { const SourceLocation& source_location) {
// We should never have a violation against `child-src` or `default-src`
// directly; the effective directive should always be one of the explicit
// fetch directives.
DCHECK_NE(directive_name, CSPDirective::DefaultSrc);
DCHECK_NE(directive_name, CSPDirective::ChildSrc);
// For security reasons, some urls must not be disclosed. This includes the // For security reasons, some urls must not be disclosed. This includes the
// blocked url and the source location of the error. Care must be taken to // blocked url and the source location of the error. Care must be taken to
// ensure that these are not transmitted between different cross-origin // ensure that these are not transmitted between different cross-origin
// renderers. // renderers.
GURL safe_url = url; GURL blocked_url = (directive_name == CSPDirective::FrameAncestors)
? GURL(context->self_source()->ToString())
: url;
SourceLocation safe_source_location = source_location; SourceLocation safe_source_location = source_location;
context->SanitizeDataForUseInCspViolation( context->SanitizeDataForUseInCspViolation(has_followed_redirect,
has_followed_redirect, directive_name, &safe_url, &safe_source_location); directive_name, &blocked_url,
&safe_source_location);
std::stringstream message; std::stringstream message;
if (policy.header.type == network::mojom::ContentSecurityPolicyType::kReport) if (policy.header.type == network::mojom::ContentSecurityPolicyType::kReport)
message << "[Report Only] "; message << "[Report Only] ";
if (directive_name == CSPDirective::FormAction) message << base::ReplaceStringPlaceholders(
message << "Refused to send form data to '"; ErrorMessage(directive_name),
else if (directive_name == CSPDirective::FrameSrc) {ElideURLForReportViolation(blocked_url), directive.ToString()}, nullptr);
message << "Refused to frame '";
else if (directive_name == CSPDirective::NavigateTo)
message << "Refused to navigate to '";
message << ElideURLForReportViolation(safe_url)
<< "' because it violates the following Content Security Policy "
"directive: \""
<< directive.ToString() << "\".";
if (directive.name != directive_name) if (directive.name != directive_name) {
message << " Note that '" << CSPDirective::NameToString(directive_name) message << " Note that '" << CSPDirective::NameToString(directive_name)
<< "' was not explicitly set, so '" << "' was not explicitly set, so '"
<< CSPDirective::NameToString(directive.name) << CSPDirective::NameToString(directive.name)
<< "' is used as a fallback."; << "' is used as a fallback.";
}
message << "\n"; message << "\n";
context->ReportContentSecurityPolicyViolation(CSPViolationParams( context->ReportContentSecurityPolicyViolation(CSPViolationParams(
CSPDirective::NameToString(directive.name), CSPDirective::NameToString(directive.name),
CSPDirective::NameToString(directive_name), message.str(), safe_url, CSPDirective::NameToString(directive_name), message.str(), blocked_url,
policy.report_endpoints, policy.use_reporting_api, policy.report_endpoints, policy.use_reporting_api,
policy.header.header_value, policy.header.type, has_followed_redirect, policy.header.header_value, policy.header.type, has_followed_redirect,
safe_source_location)); safe_source_location));
...@@ -156,6 +173,16 @@ ContentSecurityPolicy::ContentSecurityPolicy( ...@@ -156,6 +173,16 @@ ContentSecurityPolicy::ContentSecurityPolicy(
report_endpoints(report_endpoints), report_endpoints(report_endpoints),
use_reporting_api(use_reporting_api) {} use_reporting_api(use_reporting_api) {}
// TODO(arthursonzogni): Add the |header| to the network ContentSecurityPolicy
// struct.
ContentSecurityPolicy::ContentSecurityPolicy(
network::mojom::ContentSecurityPolicyPtr csp)
: report_endpoints(std::move(csp->report_endpoints)),
use_reporting_api(csp->use_reporting_api) {
for (auto& directive : csp->directives)
directives.emplace_back(std::move(directive));
}
ContentSecurityPolicy::ContentSecurityPolicy( ContentSecurityPolicy::ContentSecurityPolicy(
const ContentSecurityPolicy& other) = default; const ContentSecurityPolicy& other) = default;
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/common/content_security_policy/csp_directive.h" #include "content/common/content_security_policy/csp_directive.h"
#include "content/common/content_security_policy_header.h" #include "content/common/content_security_policy_header.h"
#include "services/network/public/mojom/content_security_policy.mojom-forward.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace content { namespace content {
...@@ -28,6 +29,7 @@ struct CONTENT_EXPORT ContentSecurityPolicy { ...@@ -28,6 +29,7 @@ struct CONTENT_EXPORT ContentSecurityPolicy {
const std::vector<CSPDirective>& directives, const std::vector<CSPDirective>& directives,
const std::vector<std::string>& report_endpoints, const std::vector<std::string>& report_endpoints,
bool use_reporting_api); bool use_reporting_api);
explicit ContentSecurityPolicy(network::mojom::ContentSecurityPolicyPtr);
ContentSecurityPolicy(const ContentSecurityPolicy&); ContentSecurityPolicy(const ContentSecurityPolicy&);
~ContentSecurityPolicy(); ~ContentSecurityPolicy();
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "content/common/content_security_policy/csp_directive.h" #include "content/common/content_security_policy/csp_directive.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
namespace content { namespace content {
...@@ -13,6 +14,10 @@ CSPDirective::CSPDirective(Name name, const CSPSourceList& source_list) ...@@ -13,6 +14,10 @@ CSPDirective::CSPDirective(Name name, const CSPSourceList& source_list)
CSPDirective::CSPDirective(const CSPDirective&) = default; CSPDirective::CSPDirective(const CSPDirective&) = default;
CSPDirective::CSPDirective(network::mojom::CSPDirectivePtr directive)
: name(static_cast<Name>(directive->name)),
source_list(std::move(directive->source_list)) {}
std::string CSPDirective::ToString() const { std::string CSPDirective::ToString() const {
return NameToString(name) + " " + source_list.ToString(); return NameToString(name) + " " + source_list.ToString();
} }
......
...@@ -40,6 +40,7 @@ struct CONTENT_EXPORT CSPDirective { ...@@ -40,6 +40,7 @@ struct CONTENT_EXPORT CSPDirective {
CSPDirective(); CSPDirective();
CSPDirective(Name name, const CSPSourceList& source_list); CSPDirective(Name name, const CSPSourceList& source_list);
CSPDirective(const CSPDirective&); CSPDirective(const CSPDirective&);
explicit CSPDirective(network::mojom::CSPDirectivePtr directive);
Name name; Name name;
CSPSourceList source_list; CSPSourceList source_list;
......
...@@ -173,13 +173,13 @@ CSPSource::CSPSource(const std::string& scheme, ...@@ -173,13 +173,13 @@ CSPSource::CSPSource(const std::string& scheme,
DCHECK(!is_port_wildcard || port == url::PORT_UNSPECIFIED); DCHECK(!is_port_wildcard || port == url::PORT_UNSPECIFIED);
} }
CSPSource::CSPSource(const network::mojom::CSPSource& csp_source) CSPSource::CSPSource(network::mojom::CSPSourcePtr csp_source)
: CSPSource(csp_source.scheme, : CSPSource(csp_source->scheme,
csp_source.host, csp_source->host,
csp_source.is_host_wildcard, csp_source->is_host_wildcard,
csp_source.port, csp_source->port,
csp_source.is_port_wildcard, csp_source->is_port_wildcard,
csp_source.path) {} csp_source->path) {}
CSPSource::CSPSource(const CSPSource& source) = default; CSPSource::CSPSource(const CSPSource& source) = default;
CSPSource::~CSPSource() = default; CSPSource::~CSPSource() = default;
......
...@@ -32,7 +32,7 @@ struct CONTENT_EXPORT CSPSource { ...@@ -32,7 +32,7 @@ struct CONTENT_EXPORT CSPSource {
int port, int port,
bool is_port_wildcard, bool is_port_wildcard,
const std::string& path); const std::string& path);
explicit CSPSource(const network::mojom::CSPSource& csp_source); explicit CSPSource(network::mojom::CSPSourcePtr csp_source);
CSPSource(const CSPSource& source); CSPSource(const CSPSource& source);
~CSPSource(); ~CSPSource();
......
...@@ -36,13 +36,14 @@ CSPSourceList::CSPSourceList(bool allow_self, ...@@ -36,13 +36,14 @@ CSPSourceList::CSPSourceList(bool allow_self,
allow_response_redirects(allow_response_redirects), allow_response_redirects(allow_response_redirects),
sources(sources) {} sources(sources) {}
CSPSourceList::CSPSourceList( CSPSourceList::CSPSourceList(network::mojom::CSPSourceListPtr csp_source_list)
const network::mojom::CSPSourceList& csp_source_list) : allow_self(csp_source_list->allow_self),
: allow_self(csp_source_list.allow_self), allow_star(csp_source_list->allow_star),
allow_star(csp_source_list.allow_star), // TODO(arthursonzogni): Add the allow_response_redirect to the network
allow_response_redirects(false) { // CSPSourceList struct.
for (auto& source : csp_source_list.sources) allow_response_redirects(true) {
sources.push_back(CSPSource(*source)); for (auto& source : csp_source_list->sources)
sources.push_back(CSPSource(std::move(source)));
} }
CSPSourceList::CSPSourceList(const CSPSourceList&) = default; CSPSourceList::CSPSourceList(const CSPSourceList&) = default;
......
...@@ -21,7 +21,7 @@ struct CONTENT_EXPORT CSPSourceList { ...@@ -21,7 +21,7 @@ struct CONTENT_EXPORT CSPSourceList {
bool allow_star, bool allow_star,
bool allow_response_redirects, bool allow_response_redirects,
std::vector<CSPSource> source_list); std::vector<CSPSource> source_list);
explicit CSPSourceList(const network::mojom::CSPSourceList& csp_source_list); explicit CSPSourceList(network::mojom::CSPSourceListPtr csp_source_list);
CSPSourceList(const CSPSourceList&); CSPSourceList(const CSPSourceList&);
~CSPSourceList(); ~CSPSourceList();
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/i18n/icu_util.h" #include "base/i18n/icu_util.h"
#include "net/http/http_response_headers.h" #include "net/http/http_response_headers.h"
#include "testing/libfuzzer/libfuzzer_exports.h" #include "testing/libfuzzer/libfuzzer_exports.h"
#include "url/gurl.h"
namespace { namespace {
......
...@@ -23,6 +23,18 @@ using DirectivesMap = base::flat_map<base::StringPiece, base::StringPiece>; ...@@ -23,6 +23,18 @@ using DirectivesMap = base::flat_map<base::StringPiece, base::StringPiece>;
namespace { namespace {
// Looks by name for a directive in a list of directives.
// If it is not found, returns nullptr.
static mojom::CSPDirectivePtr* FindDirective(
mojom::CSPDirective::Name name,
std::vector<mojom::CSPDirectivePtr>* directives) {
for (auto& directive : *directives) {
if (directive->name == name)
return &directive;
}
return nullptr;
}
// Parses a "Content-Security-Policy" header. // Parses a "Content-Security-Policy" header.
// Returns a map to the directives found. // Returns a map to the directives found.
DirectivesMap ParseHeaderValue(base::StringPiece header) { DirectivesMap ParseHeaderValue(base::StringPiece header) {
...@@ -215,7 +227,7 @@ bool ParseAncestorSource(base::StringPiece expression, ...@@ -215,7 +227,7 @@ bool ParseAncestorSource(base::StringPiece expression,
// Parse ancestor-source-list grammar. // Parse ancestor-source-list grammar.
// https://www.w3.org/TR/CSP3/#directive-frame-ancestors // https://www.w3.org/TR/CSP3/#directive-frame-ancestors
mojom::CSPSourceListPtr ParseFrameAncestorsDirective( mojom::CSPSourceListPtr ParseFrameAncestorsSourceList(
base::StringPiece frame_ancestors_value) { base::StringPiece frame_ancestors_value) {
base::StringPiece value = base::TrimString( base::StringPiece value = base::TrimString(
frame_ancestors_value, base::kWhitespaceASCII, base::TRIM_ALL); frame_ancestors_value, base::kWhitespaceASCII, base::TRIM_ALL);
...@@ -260,17 +272,29 @@ mojom::CSPSourceListPtr ParseFrameAncestorsDirective( ...@@ -260,17 +272,29 @@ mojom::CSPSourceListPtr ParseFrameAncestorsDirective(
// TODO(lfg): The report-to should be treated as a single token according to the // TODO(lfg): The report-to should be treated as a single token according to the
// spec, but this implementation accepts multiple endpoints // spec, but this implementation accepts multiple endpoints
// https://crbug.com/916265. // https://crbug.com/916265.
base::Optional<std::vector<GURL>> ParseReportDirective( bool ParseReportDirective(const GURL& request_url,
const GURL& request_url, base::StringPiece value,
base::StringPiece value) { std::vector<std::string>* report_endpoints) {
std::vector<GURL> report_endpoints;
for (const auto& uri : base::SplitStringPiece( for (const auto& uri : base::SplitStringPiece(
value, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { value, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
report_endpoints.push_back(request_url.Resolve(uri)); // There are two types of reporting directive:
if (!report_endpoints.back().is_valid()) //
return base::nullopt; // - "report-uri (uri)+"
// |uri| must be resolved relatively to the requested URL.
//
// - "report-to (endpoint)+"
// |endpoint| is an arbitrary string. It refers to an endpoint declared in
// the "Report-To" header. See https://w3c.github.io/reporting
//
// TODO(lfg): The |endpoint| for the 'report-to' directive shouldn't be
// resolved.
GURL url = request_url.Resolve(uri);
if (!url.is_valid())
return false;
report_endpoints->push_back(url.spec());
} }
return report_endpoints; return true;
} }
} // namespace } // namespace
...@@ -321,6 +345,9 @@ bool ContentSecurityPolicy::Parse(const GURL& base_url, ...@@ -321,6 +345,9 @@ bool ContentSecurityPolicy::Parse(const GURL& base_url,
// RFC7230, section 3.2.2 specifies that headers appearing multiple times can // RFC7230, section 3.2.2 specifies that headers appearing multiple times can
// be combined with a comma. Walk the header string, and parse each comma // be combined with a comma. Walk the header string, and parse each comma
// separated chunk as a separate header. // separated chunk as a separate header.
//
// TODO(arthursonzogni, lfg): The ContentSecurityPolicy policy shouldn't be
// combined. Several ContentSecurityPolicies should be produced, not one.
for (const auto& header : for (const auto& header :
base::SplitStringPiece(header_value, ",", base::TRIM_WHITESPACE, base::SplitStringPiece(header_value, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) { base::SPLIT_WANT_NONEMPTY)) {
...@@ -360,17 +387,23 @@ bool ContentSecurityPolicy::ParseFrameAncestors( ...@@ -360,17 +387,23 @@ bool ContentSecurityPolicy::ParseFrameAncestors(
// A frame-ancestors directive has already been parsed. Skip further // A frame-ancestors directive has already been parsed. Skip further
// frame-ancestors directives per // frame-ancestors directives per
// https://www.w3.org/TR/CSP3/#parse-serialized-policy. // https://www.w3.org/TR/CSP3/#parse-serialized-policy.
if (content_security_policy_ptr_->frame_ancestors) if (FindDirective(mojom::CSPDirective::Name::FrameAncestors,
return true; &(content_security_policy_ptr_->directives))) {
// TODO(arthursonzogni, lfg): Should a warning be fired to the user here?
if (auto directive = ParseFrameAncestorsDirective(frame_ancestors_value)) {
content_security_policy_ptr_->frame_ancestors = std::move(directive);
return true; return true;
} }
auto source_list = ParseFrameAncestorsSourceList(frame_ancestors_value);
// TODO(lfg): Emit a warning to the user when parsing an invalid // TODO(lfg): Emit a warning to the user when parsing an invalid
// expression. // expression.
return false; if (!source_list)
return false;
content_security_policy_ptr_->directives.push_back(mojom::CSPDirective::New(
mojom::CSPDirective::Name::FrameAncestors, std::move(source_list)));
return true;
} }
bool ContentSecurityPolicy::ParseReportEndpoint( bool ContentSecurityPolicy::ParseReportEndpoint(
...@@ -381,16 +414,15 @@ bool ContentSecurityPolicy::ParseReportEndpoint( ...@@ -381,16 +414,15 @@ bool ContentSecurityPolicy::ParseReportEndpoint(
if (!content_security_policy_ptr_->report_endpoints.empty()) if (!content_security_policy_ptr_->report_endpoints.empty())
return true; return true;
if (auto parsed_report_directive = if (!ParseReportDirective(
ParseReportDirective(base_url, header_value)) { base_url, header_value,
content_security_policy_ptr_->report_endpoints = &(content_security_policy_ptr_->report_endpoints))) {
std::move(*parsed_report_directive); // TODO(lfg): Emit a warning to the user when parsing an invalid
return true; // expression.
return false;
} }
// TODO(lfg): Emit a warning to the user when parsing an invalid return true;
// expression.
return false;
} }
} // namespace network } // namespace network
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include "base/strings/string_piece_forward.h" #include "base/strings/string_piece_forward.h"
#include "services/network/public/mojom/content_security_policy.mojom.h" #include "services/network/public/mojom/content_security_policy.mojom.h"
class GURL;
namespace net { namespace net {
class HttpResponseHeaders; class HttpResponseHeaders;
} // namespace net } // namespace net
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "services/network/public/cpp/content_security_policy.h" #include "services/network/public/cpp/content_security_policy.h"
#include "net/http/http_response_headers.h" #include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/third_party/mozilla/url_parse.h" #include "url/third_party/mozilla/url_parse.h"
namespace network { namespace network {
...@@ -47,7 +48,8 @@ static void TestCSPParser(const std::string& header, ...@@ -47,7 +48,8 @@ static void TestCSPParser(const std::string& header,
EXPECT_FALSE(policy.content_security_policy_ptr()); EXPECT_FALSE(policy.content_security_policy_ptr());
return; return;
} }
auto& frame_ancestors = policy.content_security_policy_ptr()->frame_ancestors; auto& frame_ancestors =
policy.content_security_policy_ptr()->directives[0]->source_list;
EXPECT_EQ(frame_ancestors->sources.size(), EXPECT_EQ(frame_ancestors->sources.size(),
expected_result->parsed_sources.size()); expected_result->parsed_sources.size());
for (size_t i = 0; i < expected_result->parsed_sources.size(); i++) { for (size_t i = 0; i < expected_result->parsed_sources.size(); i++) {
...@@ -179,7 +181,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) { ...@@ -179,7 +181,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
policy.Parse(GURL("https://example.com/"), *headers); policy.Parse(GURL("https://example.com/"), *headers);
auto& frame_ancestors = auto& frame_ancestors =
policy.content_security_policy_ptr()->frame_ancestors; policy.content_security_policy_ptr()->directives[0]->source_list;
EXPECT_EQ(frame_ancestors->sources.size(), 1U); EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, ""); EXPECT_EQ(frame_ancestors->sources[0]->scheme, "");
EXPECT_EQ(frame_ancestors->sources[0]->host, "example.com"); EXPECT_EQ(frame_ancestors->sources[0]->host, "example.com");
...@@ -201,7 +203,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) { ...@@ -201,7 +203,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
policy.Parse(GURL("https://example.com/"), *headers); policy.Parse(GURL("https://example.com/"), *headers);
auto& frame_ancestors = auto& frame_ancestors =
policy.content_security_policy_ptr()->frame_ancestors; policy.content_security_policy_ptr()->directives[0]->source_list;
EXPECT_EQ(frame_ancestors->sources.size(), 1U); EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, ""); EXPECT_EQ(frame_ancestors->sources[0]->scheme, "");
EXPECT_EQ(frame_ancestors->sources[0]->host, "example.org"); EXPECT_EQ(frame_ancestors->sources[0]->host, "example.org");
...@@ -224,7 +226,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) { ...@@ -224,7 +226,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
policy.Parse(GURL("https://example.com/"), *headers); policy.Parse(GURL("https://example.com/"), *headers);
auto& frame_ancestors = auto& frame_ancestors =
policy.content_security_policy_ptr()->frame_ancestors; policy.content_security_policy_ptr()->directives[0]->source_list;
EXPECT_EQ(frame_ancestors->sources.size(), 1U); EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, ""); EXPECT_EQ(frame_ancestors->sources[0]->scheme, "");
EXPECT_EQ(frame_ancestors->sources[0]->host, "example.com"); EXPECT_EQ(frame_ancestors->sources[0]->host, "example.com");
...@@ -247,7 +249,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) { ...@@ -247,7 +249,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
policy.Parse(GURL("https://example.com/"), *headers); policy.Parse(GURL("https://example.com/"), *headers);
auto& frame_ancestors = auto& frame_ancestors =
policy.content_security_policy_ptr()->frame_ancestors; policy.content_security_policy_ptr()->directives[0]->source_list;
EXPECT_EQ(frame_ancestors->sources.size(), 1U); EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, ""); EXPECT_EQ(frame_ancestors->sources[0]->scheme, "");
EXPECT_EQ(frame_ancestors->sources[0]->host, "example.org"); EXPECT_EQ(frame_ancestors->sources[0]->host, "example.org");
...@@ -271,7 +273,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) { ...@@ -271,7 +273,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
policy.Parse(GURL("https://example.com/"), *headers); policy.Parse(GURL("https://example.com/"), *headers);
auto& frame_ancestors = auto& frame_ancestors =
policy.content_security_policy_ptr()->frame_ancestors; policy.content_security_policy_ptr()->directives[0]->source_list;
EXPECT_EQ(frame_ancestors->sources.size(), 1U); EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, ""); EXPECT_EQ(frame_ancestors->sources[0]->scheme, "");
EXPECT_EQ(frame_ancestors->sources[0]->host, "example.com"); EXPECT_EQ(frame_ancestors->sources[0]->host, "example.com");
...@@ -300,7 +302,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) { ...@@ -300,7 +302,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
EXPECT_TRUE(policy.content_security_policy_ptr()->use_reporting_api); EXPECT_TRUE(policy.content_security_policy_ptr()->use_reporting_api);
auto& frame_ancestors = auto& frame_ancestors =
policy.content_security_policy_ptr()->frame_ancestors; policy.content_security_policy_ptr()->directives[0]->source_list;
EXPECT_EQ(frame_ancestors->sources.size(), 1U); EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, ""); EXPECT_EQ(frame_ancestors->sources[0]->scheme, "");
EXPECT_EQ(frame_ancestors->sources[0]->host, "example.com"); EXPECT_EQ(frame_ancestors->sources[0]->host, "example.com");
......
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
module network.mojom; module network.mojom;
import "url/mojom/url.mojom";
// The HTTP Content-Security-Policy-Report-Only response header allows web // The HTTP Content-Security-Policy-Report-Only response header allows web
// developers to experiment with policies by monitoring (but not enforcing) // developers to experiment with policies by monitoring (but not enforcing)
// their effects. These violation reports consist of JSON documents sent via an // their effects. These violation reports consist of JSON documents sent via an
...@@ -42,14 +40,30 @@ struct CSPSourceList { ...@@ -42,14 +40,30 @@ struct CSPSourceList {
bool allow_star = false; bool allow_star = false;
}; };
struct CSPDirective {
enum Name {
DefaultSrc,
ChildSrc,
FrameSrc,
FormAction,
UpgradeInsecureRequests,
NavigateTo,
FrameAncestors,
Unknown,
};
Name name;
CSPSourceList source_list;
};
struct ContentSecurityPolicy { struct ContentSecurityPolicy {
// List of sources specified in the frame-ancestors directive. array<CSPDirective> directives;
CSPSourceList? frame_ancestors;
// Whether this CSP policy uses the new reporting API. // Whether this CSP policy uses the new reporting API.
// https://w3c.github.io/reporting/ // https://w3c.github.io/reporting/
bool use_reporting_api = false; bool use_reporting_api = false;
// Set of reporting endpoints to which violation reports are sent. // Set of reporting endpoints to which violation reports are sent.
array<url.mojom.Url> report_endpoints; array<string> report_endpoints;
}; };
...@@ -71,5 +71,6 @@ struct FetchAPIResponse { ...@@ -71,5 +71,6 @@ struct FetchAPIResponse {
// In case this response had a Content-Security-Policy header, this is the // In case this response had a Content-Security-Policy header, this is the
// parsed CSP. // parsed CSP.
// TODO(lfg) Add support for multiple policies.
network.mojom.ContentSecurityPolicy? content_security_policy; network.mojom.ContentSecurityPolicy? content_security_policy;
}; };
include_rules = [ include_rules = [
"+gin/public", "+gin/public",
"+mojo/public/cpp/system/data_pipe_utils.h",
"+mojo/public/cpp/bindings/binding_set.h", "+mojo/public/cpp/bindings/binding_set.h",
"+mojo/public/cpp/system/data_pipe.h", "+mojo/public/cpp/system/data_pipe.h",
"+mojo/public/cpp/system/data_pipe_utils.h",
"+mojo/public/cpp/system/simple_watcher.h", "+mojo/public/cpp/system/simple_watcher.h",
"+net/base/request_priority.h", "+net/base/request_priority.h",
"+services/network/public/cpp/content_security_policy.h", "+services/network/public/cpp/content_security_policy.h",
"+url/gurl.h",
] ]
...@@ -15,12 +15,61 @@ ...@@ -15,12 +15,61 @@
#include "third_party/blink/renderer/platform/loader/cors/cors.h" #include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.h" #include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.h"
#include "third_party/blink/renderer/platform/network/http_names.h" #include "third_party/blink/renderer/platform/network/http_names.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "url/gurl.h"
using Type = network::mojom::FetchResponseType; using Type = network::mojom::FetchResponseType;
using ResponseSource = network::mojom::FetchResponseSource; using ResponseSource = network::mojom::FetchResponseSource;
// TODO(lfg): Stop converting from/to blink type. Instead use mojo to
// automagically convert this.
namespace network {
namespace mojom {
blink::CSPSourcePtr ConvertToBlink(CSPSourcePtr source) {
return blink::CSPSource::New(
String::FromUTF8(source->scheme), String::FromUTF8(source->host),
source->port, String::FromUTF8(source->path), source->is_host_wildcard,
source->is_port_wildcard);
}
blink::CSPSourceListPtr ConvertToBlink(CSPSourceListPtr source_list) {
WTF::Vector<blink::CSPSourcePtr> sources;
for (auto& it : source_list->sources)
sources.push_back(ConvertToBlink(std::move(it)));
return blink::CSPSourceList::New(std::move(sources), source_list->allow_self,
source_list->allow_star);
}
blink::CSPDirective::Name ConvertToBlink(CSPDirective::Name name) {
return static_cast<blink::CSPDirective::Name>(name);
}
blink::CSPDirectivePtr ConvertToBlink(CSPDirectivePtr csp) {
return blink::CSPDirective::New(ConvertToBlink(csp->name),
ConvertToBlink(std::move(csp->source_list)));
}
blink::ContentSecurityPolicyPtr ConvertToBlink(ContentSecurityPolicyPtr csp) {
WTF::Vector<blink::CSPDirectivePtr> directives;
for (auto& directive : csp->directives)
directives.push_back(ConvertToBlink(std::move(directive)));
WTF::Vector<WTF::String> report_endpoints;
for (auto& endpoint : csp->report_endpoints)
report_endpoints.push_back(String::FromUTF8(endpoint));
return blink::ContentSecurityPolicy::New(std::move(directives),
csp->use_reporting_api,
std::move(report_endpoints));
}
} // namespace mojom
} // namespace network
namespace blink { namespace blink {
namespace { namespace {
...@@ -269,6 +318,7 @@ mojom::blink::FetchAPIResponsePtr FetchResponseData::PopulateFetchAPIResponse( ...@@ -269,6 +318,7 @@ mojom::blink::FetchAPIResponsePtr FetchResponseData::PopulateFetchAPIResponse(
// Check if there's a Content-Security-Policy header and parse it if // Check if there's a Content-Security-Policy header and parse it if
// necessary. // necessary.
// TODO(lfg). What about report only header?
if (base::FeatureList::IsEnabled( if (base::FeatureList::IsEnabled(
network::features::kOutOfBlinkFrameAncestors)) { network::features::kOutOfBlinkFrameAncestors)) {
String content_security_policy_header; String content_security_policy_header;
...@@ -278,40 +328,8 @@ mojom::blink::FetchAPIResponsePtr FetchResponseData::PopulateFetchAPIResponse( ...@@ -278,40 +328,8 @@ mojom::blink::FetchAPIResponsePtr FetchResponseData::PopulateFetchAPIResponse(
if (policy.Parse(request_url, if (policy.Parse(request_url,
StringUTF8Adaptor(content_security_policy_header) StringUTF8Adaptor(content_security_policy_header)
.AsStringPiece())) { .AsStringPiece())) {
// Convert network::mojom::ContentSecurityPolicy to
// network::mojom::blink::ContentSecurityPolicy.
auto blink_frame_ancestors =
network::mojom::blink::CSPSourceList::New();
WTF::Vector<KURL> report_endpoints;
const network::mojom::CSPSourceListPtr& frame_ancestors_directive =
policy.content_security_policy_ptr()->frame_ancestors;
if (frame_ancestors_directive) {
for (auto& csp_source : frame_ancestors_directive->sources) {
blink_frame_ancestors->sources.push_back(
network::mojom::blink::CSPSource::New(
String::FromUTF8(csp_source->scheme),
String::FromUTF8(csp_source->host), csp_source->port,
String::FromUTF8(csp_source->path),
csp_source->is_host_wildcard,
csp_source->is_port_wildcard));
}
blink_frame_ancestors->allow_self =
frame_ancestors_directive->allow_self;
blink_frame_ancestors->allow_star =
frame_ancestors_directive->allow_star;
}
for (auto& endpoints :
policy.content_security_policy_ptr()->report_endpoints) {
report_endpoints.push_back(endpoints);
}
response->content_security_policy = response->content_security_policy =
network::mojom::blink::ContentSecurityPolicy::New( ConvertToBlink(policy.TakeContentSecurityPolicy());
std::move(blink_frame_ancestors),
policy.content_security_policy_ptr()->use_reporting_api,
std::move(report_endpoints));
} }
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment