blob: 909de5c3091cd4012f652c301a4d54b007074212 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/stl_util.h"
#include "content/common/content_security_policy/csp_context.h"
#include "content/common/content_security_policy_header.h"
#include "content/common/navigation_params.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
class CSPContextTest : public CSPContext {
public:
CSPContextTest() : CSPContext() {}
const std::vector<CSPViolationParams>& violations() { return violations_; }
void AddSchemeToBypassCSP(const std::string& scheme) {
scheme_to_bypass_.push_back(scheme);
}
bool SchemeShouldBypassCSP(const base::StringPiece& scheme) override {
return base::ContainsValue(scheme_to_bypass_, scheme);
}
private:
void ReportContentSecurityPolicyViolation(
const CSPViolationParams& violation_params) override {
violations_.push_back(violation_params);
}
std::vector<CSPViolationParams> violations_;
std::vector<std::string> scheme_to_bypass_;
DISALLOW_COPY_AND_ASSIGN(CSPContextTest);
};
ContentSecurityPolicyHeader EmptyCspHeader() {
return ContentSecurityPolicyHeader(
std::string(), blink::mojom::ContentSecurityPolicyType::kEnforce,
blink::kWebContentSecurityPolicySourceHTTP);
}
} // namespace
TEST(ContentSecurityPolicy, NoDirective) {
CSPContextTest context;
std::vector<std::string> report_end_points; // empty
ContentSecurityPolicy policy(EmptyCspHeader(), std::vector<CSPDirective>(),
report_end_points, false);
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FormAction, GURL("http://www.example.com"), false,
false, &context, SourceLocation(), true));
ASSERT_EQ(0u, context.violations().size());
}
TEST(ContentSecurityPolicy, ReportViolation) {
CSPContextTest context;
// source = "www.example.com"
CSPSource source("", "www.example.com", false, url::PORT_UNSPECIFIED, false,
"");
CSPSourceList source_list(false, false, false, {source});
CSPDirective directive(CSPDirective::FormAction, source_list);
std::vector<std::string> report_end_points; // empty
ContentSecurityPolicy policy(EmptyCspHeader(), {directive}, report_end_points,
false);
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FormAction, GURL("http://www.not-example.com"),
false, false, &context, SourceLocation(), true));
ASSERT_EQ(1u, context.violations().size());
const char console_message[] =
"Refused to send form data to 'http://www.not-example.com/' because it "
"violates the following Content Security Policy directive: \"form-action "
"www.example.com\".\n";
EXPECT_EQ(console_message, context.violations()[0].console_message);
}
TEST(ContentSecurityPolicy, DirectiveFallback) {
CSPSource source_a("http", "a.com", false, url::PORT_UNSPECIFIED, false, "");
CSPSource source_b("http", "b.com", false, url::PORT_UNSPECIFIED, false, "");
CSPSourceList source_list_a(false, false, false, {source_a});
CSPSourceList source_list_b(false, false, false, {source_b});
std::vector<std::string> report_end_points; // Empty.
{
CSPContextTest context;
ContentSecurityPolicy policy(
EmptyCspHeader(),
{CSPDirective(CSPDirective::DefaultSrc, source_list_a)},
report_end_points, false);
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("http://b.com"), false, false,
&context, SourceLocation(), false));
ASSERT_EQ(1u, context.violations().size());
const char console_message[] =
"Refused to frame 'http://b.com/' because it violates "
"the following Content Security Policy directive: \"default-src "
"http://a.com\". Note that 'frame-src' was not explicitly "
"set, so 'default-src' is used as a fallback.\n";
EXPECT_EQ(console_message, context.violations()[0].console_message);
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("http://a.com"), false, false,
&context, SourceLocation(), false));
}
{
CSPContextTest context;
ContentSecurityPolicy policy(
EmptyCspHeader(), {CSPDirective(CSPDirective::ChildSrc, source_list_a)},
report_end_points, false);
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("http://b.com"), false, false,
&context, SourceLocation(), false));
ASSERT_EQ(1u, context.violations().size());
const char console_message[] =
"Refused to frame 'http://b.com/' because it violates "
"the following Content Security Policy directive: \"child-src "
"http://a.com\". Note that 'frame-src' was not explicitly "
"set, so 'child-src' is used as a fallback.\n";
EXPECT_EQ(console_message, context.violations()[0].console_message);
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("http://a.com"), false, false,
&context, SourceLocation(), false));
}
{
CSPContextTest context;
CSPSourceList source_list(false, false, false, {source_a, source_b});
ContentSecurityPolicy policy(
EmptyCspHeader(),
{CSPDirective(CSPDirective::FrameSrc, {source_list_a}),
CSPDirective(CSPDirective::ChildSrc, {source_list_b})},
report_end_points, false);
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("http://a.com"), false, false,
&context, SourceLocation(), false));
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("http://b.com"), false, false,
&context, SourceLocation(), false));
ASSERT_EQ(1u, context.violations().size());
const char console_message[] =
"Refused to frame 'http://b.com/' because it violates "
"the following Content Security Policy directive: \"frame-src "
"http://a.com\".\n";
EXPECT_EQ(console_message, context.violations()[0].console_message);
}
}
TEST(ContentSecurityPolicy, RequestsAllowedWhenBypassingCSP) {
CSPContextTest context;
std::vector<std::string> report_end_points; // empty
CSPSource source("https", "example.com", false, url::PORT_UNSPECIFIED, false,
"");
CSPSourceList source_list(false, false, false, {source});
ContentSecurityPolicy policy(
EmptyCspHeader(), {CSPDirective(CSPDirective::DefaultSrc, source_list)},
report_end_points, false);
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("https://example.com/"), false,
false, &context, SourceLocation(), false));
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("https://not-example.com/"), false,
false, &context, SourceLocation(), false));
// Register 'https' as bypassing CSP, which should now bypass it entirely.
context.AddSchemeToBypassCSP("https");
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("https://example.com/"), false,
false, &context, SourceLocation(), false));
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("https://not-example.com/"), false,
false, &context, SourceLocation(), false));
}
TEST(ContentSecurityPolicy, RequestsAllowedWhenHostMixedCase) {
CSPContextTest context;
std::vector<std::string> report_end_points; // empty
CSPSource source("https", "ExAmPle.com", false, url::PORT_UNSPECIFIED, false,
"");
CSPSourceList source_list(false, false, false, {source});
ContentSecurityPolicy policy(
EmptyCspHeader(), {CSPDirective(CSPDirective::DefaultSrc, source_list)},
report_end_points, false);
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("https://example.com/"), false,
false, &context, SourceLocation(), false));
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("https://not-example.com/"), false,
false, &context, SourceLocation(), false));
}
TEST(ContentSecurityPolicy, FilesystemAllowedWhenBypassingCSP) {
CSPContextTest context;
std::vector<std::string> report_end_points; // empty
CSPSource source("https", "example.com", false, url::PORT_UNSPECIFIED, false,
"");
CSPSourceList source_list(false, false, false, {source});
ContentSecurityPolicy policy(
EmptyCspHeader(), {CSPDirective(CSPDirective::DefaultSrc, source_list)},
report_end_points, false);
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc,
GURL("filesystem:https://example.com/file.txt"), false, false, &context,
SourceLocation(), false));
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc,
GURL("filesystem:https://not-example.com/file.txt"), false, false,
&context, SourceLocation(), false));
// Register 'https' as bypassing CSP, which should now bypass it entirely.
context.AddSchemeToBypassCSP("https");
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc,
GURL("filesystem:https://example.com/file.txt"), false, false, &context,
SourceLocation(), false));
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc,
GURL("filesystem:https://not-example.com/file.txt"), false, false,
&context, SourceLocation(), false));
}
TEST(ContentSecurityPolicy, BlobAllowedWhenBypassingCSP) {
CSPContextTest context;
std::vector<std::string> report_end_points; // empty
CSPSource source("https", "example.com", false, url::PORT_UNSPECIFIED, false,
"");
CSPSourceList source_list(false, false, false, {source});
ContentSecurityPolicy policy(
EmptyCspHeader(), {CSPDirective(CSPDirective::DefaultSrc, source_list)},
report_end_points, false);
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("blob:https://example.com/"), false,
false, &context, SourceLocation(), false));
EXPECT_FALSE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("blob:https://not-example.com/"),
false, false, &context, SourceLocation(), false));
// Register 'https' as bypassing CSP, which should now bypass it entirely.
context.AddSchemeToBypassCSP("https");
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("blob:https://example.com/"), false,
false, &context, SourceLocation(), false));
EXPECT_TRUE(ContentSecurityPolicy::Allow(
policy, CSPDirective::FrameSrc, GURL("blob:https://not-example.com/"),
false, false, &context, SourceLocation(), false));
}
TEST(ContentSecurityPolicy, ShouldUpgradeInsecureRequest) {
std::vector<std::string> report_end_points; // empty
CSPSource source("https", "example.com", false, url::PORT_UNSPECIFIED, false,
"");
CSPSourceList source_list(false, false, false, {source});
ContentSecurityPolicy policy(
EmptyCspHeader(), {CSPDirective(CSPDirective::DefaultSrc, source_list)},
report_end_points, false);
EXPECT_FALSE(ContentSecurityPolicy::ShouldUpgradeInsecureRequest(policy));
policy.directives.push_back(
CSPDirective(CSPDirective::UpgradeInsecureRequests, CSPSourceList()));
EXPECT_TRUE(ContentSecurityPolicy::ShouldUpgradeInsecureRequest(policy));
}
TEST(ContentSecurityPolicy, NavigateToChecks) {
CSPContextTest context;
std::vector<std::string> report_end_points; // empty
CSPSource example("https", "example.test", false, url::PORT_UNSPECIFIED,
false, "");
CSPSourceList none_source_list(false, false, false, {});
CSPSourceList example_source_list(false, false, false, {example});
CSPSourceList self_source_list(true, false, false, {});
CSPSourceList redirects_source_list(false, false, true, {});
CSPSourceList redirects_example_source_list(false, false, true, {example});
context.SetSelf(example);
struct TestCase {
const CSPSourceList& navigate_to_list;
const GURL& url;
bool is_response_check;
bool expected;
bool is_form_submission;
const CSPSourceList* form_action_list;
} cases[] = {
// Basic source matching.
{none_source_list, GURL("https://example.test"), false, false, false,
nullptr},
{example_source_list, GURL("https://example.test"), false, true, false,
nullptr},
{example_source_list, GURL("https://not-example.test"), false, false,
false, nullptr},
{self_source_list, GURL("https://example.test"), false, true, false,
nullptr},
// Checking allow_redirect flag interactions.
{redirects_source_list, GURL("https://example.test"), false, true, false,
nullptr},
{redirects_source_list, GURL("https://example.test"), true, false, false,
nullptr},
{redirects_example_source_list, GURL("https://example.test"), false, true,
false, nullptr},
{redirects_example_source_list, GURL("https://example.test"), true, true,
false, nullptr},
// Interaction with form-action
// Form submission without form-action present
{none_source_list, GURL("https://example.test"), false, false, true,
nullptr},
{example_source_list, GURL("https://example.test"), false, true, true,
nullptr},
{example_source_list, GURL("https://not-example.test"), false, false,
true, nullptr},
{self_source_list, GURL("https://example.test"), false, true, true,
nullptr},
// Form submission with form-action present
{none_source_list, GURL("https://example.test"), false, true, true,
&example_source_list},
{example_source_list, GURL("https://example.test"), false, true, true,
&example_source_list},
{example_source_list, GURL("https://not-example.test"), false, true, true,
&example_source_list},
{self_source_list, GURL("https://example.test"), false, true, true,
&example_source_list},
};
for (const auto& test : cases) {
std::vector<CSPDirective> directives;
directives.push_back(
CSPDirective(CSPDirective::NavigateTo, test.navigate_to_list));
if (test.form_action_list)
directives.push_back(
CSPDirective(CSPDirective::FormAction, *(test.form_action_list)));
ContentSecurityPolicy policy(EmptyCspHeader(), directives,
report_end_points, false);
EXPECT_EQ(test.expected, ContentSecurityPolicy::Allow(
policy, CSPDirective::NavigateTo, test.url,
true, test.is_response_check, &context,
SourceLocation(), test.is_form_submission));
EXPECT_EQ(test.expected, ContentSecurityPolicy::Allow(
policy, CSPDirective::NavigateTo, test.url,
false, test.is_response_check, &context,
SourceLocation(), test.is_form_submission));
}
}
} // namespace content