| // 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 |