blob: 8e512d6a5a83edef2805153ff2674ec019567497 [file] [log] [blame]
// Copyright 2019 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 <memory>
#include <utility>
#include "base/strings/strcat.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_task_environment.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/network/network_context.h"
#include "services/network/network_service.h"
#include "services/network/origin_policy/origin_policy_constants.h"
#include "services/network/origin_policy/origin_policy_fetcher.h"
#include "services/network/origin_policy/origin_policy_header_values.h"
#include "services/network/origin_policy/origin_policy_manager.h"
#include "services/network/origin_policy/origin_policy_parser.h"
#include "services/network/public/cpp/origin_policy.h"
#include "services/network/test/test_network_service_client.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace {
void DummyRetrieveOriginPolicyCallback(const network::OriginPolicy& result) {}
} // namespace
class OriginPolicyFetcherTest : public testing::Test {
public:
OriginPolicyFetcherTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::IO) {
network_service_ = NetworkService::CreateForTesting();
auto context_params = mojom::NetworkContextParams::New();
// Use a fixed proxy config, to avoid dependencies on local network
// configuration.
context_params->initial_proxy_config =
net::ProxyConfigWithAnnotation::CreateDirect();
network_context_ = std::make_unique<NetworkContext>(
network_service_.get(), mojo::MakeRequest(&network_context_ptr_),
std::move(context_params));
mojom::URLLoaderFactoryParamsPtr params =
mojom::URLLoaderFactoryParams::New();
params->process_id = mojom::kBrowserProcessId;
params->is_corb_enabled = false;
network_context_->CreateURLLoaderFactory(
mojo::MakeRequest(&url_loader_factory_), std::move(params));
manager_ = std::make_unique<OriginPolicyManager>(network_context_.get());
test_server_.RegisterRequestHandler(base::BindRepeating(
&OriginPolicyFetcherTest::HandleResponse, base::Unretained(this)));
EXPECT_TRUE(test_server_.Start());
test_server_origin_ = url::Origin::Create(test_server_.base_url());
}
const url::Origin& test_server_origin() const { return test_server_origin_; }
OriginPolicyManager* manager() { return manager_.get(); }
mojom::URLLoaderFactory* url_loader_factory() {
return url_loader_factory_.get();
}
protected:
const net::test_server::EmbeddedTestServer& test_server() const {
return test_server_;
}
void SetUpDefaultPolicyResponse(const std::string& manifest_body) {
default_policy_response_is_redirect_ = false;
default_policy_extra_info_ = manifest_body;
}
void SetUpDefaultPolicyRedirect(const std::string& location_path) {
default_policy_response_is_redirect_ = true;
default_policy_extra_info_ = test_server_.GetURL(location_path).spec();
}
private:
// The test server will know how to respond to the following requests:
// `/.well-known/origin-policy` => use the response that was setup via
// SetUpDefaultPolicyRedirect() or SetUpDefaultPolicyResponse()
// `/.well-known/origin-policy/policy-1` => 200, body: R"({ "feature-policy":
// ["geolocation http://example1.com"] })"
// `/.well-known/origin-policy/policy-2` => 200, body: R"({ "feature-policy":
// ["geolocation http://example2.com"] })"
// `/.well-known/origin-policy/redirect-policy` => 302 redirect to policy-1
std::unique_ptr<net::test_server::HttpResponse> HandleResponse(
const net::test_server::HttpRequest& request) {
std::unique_ptr<net::test_server::BasicHttpResponse> response =
std::make_unique<net::test_server::BasicHttpResponse>();
if (request.relative_url == "/.well-known/origin-policy") {
if (default_policy_response_is_redirect_) {
response->set_code(net::HTTP_FOUND);
response->AddCustomHeader("Location", default_policy_extra_info_);
} else {
response->set_code(net::HTTP_OK);
response->set_content(default_policy_extra_info_);
}
} else if (request.relative_url == "/.well-known/origin-policy/policy-1") {
response->set_code(net::HTTP_OK);
response->set_content(
R"({ "feature-policy": ["geolocation http://example1.com"] })");
} else if (request.relative_url == "/.well-known/origin-policy/policy-2") {
response->set_code(net::HTTP_OK);
response->set_content(
R"({ "feature-policy": ["geolocation http://example2.com"] })");
} else if (request.relative_url ==
"/.well-known/origin-policy/redirect-policy") {
response->set_code(net::HTTP_FOUND);
response->AddCustomHeader(
"Location",
test_server_.GetURL("/.well-known/origin-policy/policy-1").spec());
} else {
response->set_code(net::HTTP_NOT_FOUND);
}
return std::move(response);
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<NetworkService> network_service_;
std::unique_ptr<NetworkContext> network_context_;
mojom::NetworkContextPtr network_context_ptr_;
network::mojom::URLLoaderFactoryPtr url_loader_factory_;
net::test_server::EmbeddedTestServer test_server_;
url::Origin test_server_origin_;
std::unique_ptr<OriginPolicyManager> manager_;
bool default_policy_response_is_redirect_ = false;
std::string default_policy_extra_info_;
};
TEST_F(OriginPolicyFetcherTest, GetPolicyURL) {
url::Origin example_origin = url::Origin::Create(GURL("http://example.com/"));
EXPECT_EQ(GURL("http://example.com/.well-known/origin-policy"),
OriginPolicyFetcher::GetDefaultPolicyURL(example_origin));
EXPECT_EQ(GURL("http://example.com/.well-known/origin-policy/some-version"),
OriginPolicyFetcher::GetPolicyURL("some-version", example_origin));
}
TEST_F(OriginPolicyFetcherTest, IsValidRedirect) {
const struct {
const std::string initial_version;
const std::string redirect_path;
const bool expected;
} kTests[] = {
// Default policy is only allowed to redirect to valid policy version.
{"", "/.well-known/origin-policy/some-policy", true},
{"", "/not-in-well-known/origin-policy/some-policy", false},
{"", "/.well-known/origin-policy", false},
{"", "/.well-known/a/b", false},
{"", "/.well-known/origin-policy-foo", false},
{"", "/.well-known/return-policy", false},
{"", "/.well-known/origin-policy", false},
// Specific version policy is not allowed to redirect to other policy.
{"some-policy", "/.well-known/origin-policy/other-policy", false},
{"some-policy", "/.well-known/origin-policy", false},
{"some-policy", "/.well-known/origin-policy/some-policy", false},
{"other-policy", "/.well-known/origin-policy/some-policy/other-policy",
false},
// Query strings/hashes not allowed.
{"", "/.well-known/origin-policy/some-policy?param=value", false},
{"", "/.well-known/origin-policy/some-policy?%20", false},
{"", "/.well-known/origin-policy?some-policy", false},
{"", "/.well-known/origin-policy?version=some-policy", false},
{"", "/.well-known/origin-policy/some-policy?", false},
{"", "/.well-known/origin-policy/some-policy#h1", false},
{"", "/.well-known/origin-policy#h1", false},
{"", "/.well-known/origin-policy#some-policy", false},
{"", "/.well-known/origin-policy/some-policy#", false},
{"", "/.well-known/origin-policy/some-policy?param=value#h1", false},
{"", "/.well-known/origin-policy/some-policy?param=value#", false},
{"", "/.well-known/origin-policy/some-policy?#h1", false},
{"", "/.well-known/origin-policy/some-policy?#", false},
{"", "/.well-known/origin-policy?some-policy#", false},
{"", "/.well-known/origin-policy?#some-policy", false},
};
for (const auto& test : kTests) {
SCOPED_TRACE(test.redirect_path);
auto fetcher =
test.initial_version.empty()
? std::make_unique<OriginPolicyFetcher>(
manager(), test_server_origin(), url_loader_factory(),
base::BindOnce(&DummyRetrieveOriginPolicyCallback))
: std::make_unique<OriginPolicyFetcher>(
manager(),
OriginPolicyHeaderValues(
{.policy_version = test.initial_version}),
test_server_origin(), url_loader_factory(),
base::BindOnce(&DummyRetrieveOriginPolicyCallback));
net::RedirectInfo redirect_info;
redirect_info.new_url = test_server().GetURL(test.redirect_path);
EXPECT_EQ(test.expected, fetcher->IsValidRedirectForTesting(redirect_info));
}
}
// Helper class for starting a fetcher and saving the result
class TestOriginPolicyFetcherResult {
public:
TestOriginPolicyFetcherResult() {}
void RetrieveOriginPolicy(const std::string& version,
OriginPolicyFetcherTest* fixture) {
if (version.empty()) {
fixture->manager()->RetrieveDefaultOriginPolicy(
fixture->test_server_origin(),
base::BindOnce(&TestOriginPolicyFetcherResult::Callback,
base::Unretained(this)));
} else {
fixture->manager()->RetrieveOriginPolicy(
fixture->test_server_origin(), base::StrCat({"policy=", version}),
base::BindOnce(&TestOriginPolicyFetcherResult::Callback,
base::Unretained(this)));
}
run_loop_.Run();
}
const OriginPolicy* origin_policy_result() const {
return origin_policy_result_.get();
}
private:
void Callback(const OriginPolicy& result) {
origin_policy_result_ = std::make_unique<OriginPolicy>(result);
run_loop_.Quit();
}
base::RunLoop run_loop_;
std::unique_ptr<OriginPolicyFetcher> fetcher_;
std::unique_ptr<OriginPolicy> origin_policy_result_;
DISALLOW_COPY_AND_ASSIGN(TestOriginPolicyFetcherResult);
};
TEST_F(OriginPolicyFetcherTest, EndToEnd) {
const struct {
const std::string version;
const OriginPolicyState expected_state;
const std::string expected_raw_policy;
} kTests[] = {
{"policy-1", OriginPolicyState::kLoaded,
R"({ "feature-policy": ["geolocation http://example1.com"] })"},
{"policy-2", OriginPolicyState::kLoaded,
R"({ "feature-policy": ["geolocation http://example2.com"] })"},
{"", OriginPolicyState::kLoaded,
R"({ "feature-policy": ["geolocation http://example1.com"] })"},
{"redirect-policy", OriginPolicyState::kInvalidRedirect, ""},
{"404-version", OriginPolicyState::kCannotLoadPolicy, ""},
};
SetUpDefaultPolicyRedirect("/.well-known/origin-policy/policy-1");
for (const auto& test : kTests) {
SCOPED_TRACE(test.version);
TestOriginPolicyFetcherResult tester;
tester.RetrieveOriginPolicy(test.version, this);
EXPECT_EQ(test.expected_state, tester.origin_policy_result()->state);
if (test.expected_raw_policy.empty()) {
EXPECT_FALSE(tester.origin_policy_result()->contents);
} else {
OriginPolicyContentsPtr expected_origin_policy_contents =
OriginPolicyParser::Parse(test.expected_raw_policy);
EXPECT_EQ(expected_origin_policy_contents,
tester.origin_policy_result()->contents);
}
}
}
TEST_F(OriginPolicyFetcherTest, EndToEndInvalidRedirects) {
const struct {
std::string default_redirect_location;
} kTests[] = {
{"/.well-known/origin-policy/redirect-policy"},
{"/not-well-known/origin-policy/policy-1"},
{"/.well-known/origin-policy-something-else"},
{"/.well-known/origin-policy/policy-1/policy-2"},
{"/.well-known/origin-policy/policy-1/policy-2/policy-3"},
{"/.well-known/origin-policy/policy-1?foo=bar"},
};
for (const auto& test : kTests) {
SCOPED_TRACE(test.default_redirect_location);
SetUpDefaultPolicyRedirect(test.default_redirect_location);
TestOriginPolicyFetcherResult tester;
tester.RetrieveOriginPolicy("", this);
EXPECT_EQ(OriginPolicyState::kInvalidRedirect,
tester.origin_policy_result()->state);
EXPECT_FALSE(tester.origin_policy_result()->contents);
}
}
TEST_F(OriginPolicyFetcherTest, EndToEndDefaultNotRedirecting) {
SetUpDefaultPolicyResponse(
R"({ "feature-policy": ["geolocation http://example1.com"] })");
TestOriginPolicyFetcherResult tester;
tester.RetrieveOriginPolicy("", this);
EXPECT_EQ(OriginPolicyState::kCannotLoadPolicy,
tester.origin_policy_result()->state);
EXPECT_FALSE(tester.origin_policy_result()->contents);
}
TEST_F(OriginPolicyFetcherTest, EmptyVersionConstructor) {
EXPECT_DCHECK_DEATH(
OriginPolicyFetcher(manager(), OriginPolicyHeaderValues(),
test_server_origin(), url_loader_factory(),
base::BindOnce(&DummyRetrieveOriginPolicyCallback)));
}
} // namespace network