1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <memory>
6
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/macros.h"
10 #include "base/path_service.h"
11 #include "base/run_loop.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/test/base/in_process_browser_test.h"
18 #include "chrome/test/base/ui_test_utils.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/test/browser_test.h"
21 #include "content/public/test/browser_test_utils.h"
22 #include "net/base/host_port_pair.h"
23 #include "net/dns/mock_host_resolver.h"
24 #include "services/network/public/mojom/cors.mojom.h"
25 #include "services/network/public/mojom/cors_origin_pattern.mojom.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #include "url/gurl.h"
28 #include "url/origin.h"
29
30 namespace {
31
32 const auto kAllowSubdomains =
33 network::mojom::CorsDomainMatchMode::kAllowSubdomains;
34 const auto kDisallowSubdomains =
35 network::mojom::CorsDomainMatchMode::kDisallowSubdomains;
36
37 const char kTestPath[] = "/loader/cors_origin_access_list_test.html";
38
39 const char kTestHost[] = "crossorigin.example.com";
40 const char kTestHostInDifferentCase[] = "CrossOrigin.example.com";
41 const char kTestSubdomainHost[] = "subdomain.crossorigin.example.com";
42
43 // Tests end to end functionality of CORS access origin allow lists.
44 class CorsOriginAccessListBrowserTest : public InProcessBrowserTest {
45 protected:
46 CorsOriginAccessListBrowserTest() = default;
47
CreateWatcher()48 std::unique_ptr<content::TitleWatcher> CreateWatcher() {
49 // Register all possible result strings here.
50 std::unique_ptr<content::TitleWatcher> watcher =
51 std::make_unique<content::TitleWatcher>(web_contents(), pass_string());
52 watcher->AlsoWaitForTitle(fail_string());
53 return watcher;
54 }
55
GetReason()56 std::string GetReason() {
57 bool executing = true;
58 std::string reason;
59 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
60 script_, base::BindOnce(
61 [](bool* flag, std::string* reason, base::Value value) {
62 *flag = false;
63 DCHECK(value.is_string());
64 *reason = value.GetString();
65 },
66 base::Unretained(&executing), base::Unretained(&reason)));
67 while (executing) {
68 base::RunLoop loop;
69 loop.RunUntilIdle();
70 }
71 return reason;
72 }
73
SetAllowList(const std::string & scheme,const std::string & host,network::mojom::CorsDomainMatchMode mode)74 void SetAllowList(const std::string& scheme,
75 const std::string& host,
76 network::mojom::CorsDomainMatchMode mode) {
77 {
78 std::vector<network::mojom::CorsOriginPatternPtr> list;
79 list.push_back(network::mojom::CorsOriginPattern::New(
80 scheme, host, /*port=*/0, mode,
81 network::mojom::CorsPortMatchMode::kAllowAnyPort,
82 network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority));
83
84 base::RunLoop run_loop;
85 browser()->profile()->SetCorsOriginAccessListForOrigin(
86 url::Origin::Create(embedded_test_server()->base_url().GetOrigin()),
87 std::move(list), std::vector<network::mojom::CorsOriginPatternPtr>(),
88 run_loop.QuitClosure());
89 run_loop.Run();
90 }
91
92 {
93 std::vector<network::mojom::CorsOriginPatternPtr> list;
94 list.push_back(network::mojom::CorsOriginPattern::New(
95 scheme, host, /*port=*/0, mode,
96 network::mojom::CorsPortMatchMode::kAllowAnyPort,
97 network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority));
98
99 base::RunLoop run_loop;
100 browser()->profile()->SetCorsOriginAccessListForOrigin(
101 url::Origin::Create(
102 embedded_test_server()->GetURL(kTestHost, "/").GetOrigin()),
103 std::move(list), std::vector<network::mojom::CorsOriginPatternPtr>(),
104 run_loop.QuitClosure());
105 run_loop.Run();
106 }
107 }
108
host_ip()109 std::string host_ip() { return embedded_test_server()->base_url().host(); }
110
pass_string() const111 const base::string16& pass_string() const { return pass_string_; }
fail_string() const112 const base::string16& fail_string() const { return fail_string_; }
113
web_contents()114 content::WebContents* web_contents() {
115 return browser()->tab_strip_model()->GetActiveWebContents();
116 }
117
118 private:
SetUpOnMainThread()119 void SetUpOnMainThread() override {
120 ASSERT_TRUE(embedded_test_server()->Start());
121
122 // Setup to resolve kTestHost, kTestHostInDifferentCase and
123 // kTestSubdomainHost to the 127.0.0.1 that the test server serves.
124 host_resolver()->AddRule(kTestHost,
125 embedded_test_server()->host_port_pair().host());
126 host_resolver()->AddRule(kTestHostInDifferentCase,
127 embedded_test_server()->host_port_pair().host());
128 host_resolver()->AddRule(kTestSubdomainHost,
129 embedded_test_server()->host_port_pair().host());
130 }
131
132 const base::string16 pass_string_ = base::ASCIIToUTF16("PASS");
133 const base::string16 fail_string_ = base::ASCIIToUTF16("FAIL");
134 const base::string16 script_ = base::ASCIIToUTF16("reason");
135
136 DISALLOW_COPY_AND_ASSIGN(CorsOriginAccessListBrowserTest);
137 };
138
139 // Tests if specifying only protocol allows all hosts to pass.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,AllowAll)140 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest, AllowAll) {
141 SetAllowList("http", "", kAllowSubdomains);
142
143 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
144 EXPECT_TRUE(NavigateToURL(web_contents(),
145 embedded_test_server()->GetURL(base::StringPrintf(
146 "%s?target=%s", kTestPath, kTestHost))));
147 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle()) << GetReason();
148 }
149
150 // Tests if specifying only protocol allows all IP address based hosts to pass.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,AllowAllForIp)151 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest, AllowAllForIp) {
152 SetAllowList("http", "", kAllowSubdomains);
153
154 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
155 EXPECT_TRUE(NavigateToURL(
156 web_contents(),
157 embedded_test_server()->GetURL(
158 kTestHost,
159 base::StringPrintf("%s?target=%s", kTestPath, host_ip().c_str()))));
160 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle()) << GetReason();
161 }
162
163 // Tests if complete allow list set allows only exactly matched host to pass.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,AllowExactHost)164 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest, AllowExactHost) {
165 SetAllowList("http", kTestHost, kDisallowSubdomains);
166
167 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
168 EXPECT_TRUE(NavigateToURL(web_contents(),
169 embedded_test_server()->GetURL(base::StringPrintf(
170 "%s?target=%s", kTestPath, kTestHost))));
171 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle()) << GetReason();
172 }
173
174 // Tests if complete allow list set allows host that matches exactly, but in
175 // case insensitive way to pass.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,AllowExactHostInCaseInsensitive)176 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,
177 AllowExactHostInCaseInsensitive) {
178 SetAllowList("http", kTestHost, kDisallowSubdomains);
179
180 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
181 EXPECT_TRUE(
182 NavigateToURL(web_contents(),
183 embedded_test_server()->GetURL(base::StringPrintf(
184 "%s?target=%s", kTestPath, kTestHostInDifferentCase))));
185 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle()) << GetReason();
186 }
187
188 // Tests if complete allow list set does not allow a host with a different port
189 // to pass.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,BlockDifferentPort)190 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest, BlockDifferentPort) {
191 SetAllowList("http", kTestHost, kDisallowSubdomains);
192
193 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
194 EXPECT_TRUE(NavigateToURL(
195 web_contents(), embedded_test_server()->GetURL(base::StringPrintf(
196 "%s?target=%s&port_diff=1", kTestPath, kTestHost))));
197 EXPECT_EQ(fail_string(), watcher->WaitAndGetTitle()) << GetReason();
198 }
199
200 // Tests if complete allow list set allows a subdomain to pass if it is allowed.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,AllowSubdomain)201 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest, AllowSubdomain) {
202 SetAllowList("http", kTestHost, kAllowSubdomains);
203
204 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
205 EXPECT_TRUE(NavigateToURL(
206 web_contents(), embedded_test_server()->GetURL(base::StringPrintf(
207 "%s?target=%s", kTestPath, kTestSubdomainHost))));
208 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle()) << GetReason();
209 }
210
211 // Tests if complete allow list set does not allow a subdomain to pass.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,BlockSubdomain)212 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest, BlockSubdomain) {
213 SetAllowList("http", kTestHost, kDisallowSubdomains);
214
215 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
216 EXPECT_TRUE(NavigateToURL(
217 web_contents(), embedded_test_server()->GetURL(base::StringPrintf(
218 "%s?target=%s", kTestPath, kTestSubdomainHost))));
219 EXPECT_EQ(fail_string(), watcher->WaitAndGetTitle()) << GetReason();
220 }
221
222 // Tests if complete allow list set does not allow a host with a different
223 // protocol to pass.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,BlockDifferentProtocol)224 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,
225 BlockDifferentProtocol) {
226 SetAllowList("https", kTestHost, kDisallowSubdomains);
227
228 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
229 EXPECT_TRUE(NavigateToURL(web_contents(),
230 embedded_test_server()->GetURL(base::StringPrintf(
231 "%s?target=%s", kTestPath, kTestHost))));
232 EXPECT_EQ(fail_string(), watcher->WaitAndGetTitle()) << GetReason();
233 }
234
235 // Tests if IP address based hosts should not follow subdomain match rules.
IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,SubdomainMatchShouldNotBeAppliedForIPAddress)236 IN_PROC_BROWSER_TEST_F(CorsOriginAccessListBrowserTest,
237 SubdomainMatchShouldNotBeAppliedForIPAddress) {
238 SetAllowList("http", "*.0.0.1", kAllowSubdomains);
239
240 std::unique_ptr<content::TitleWatcher> watcher = CreateWatcher();
241 EXPECT_TRUE(NavigateToURL(
242 web_contents(),
243 embedded_test_server()->GetURL(
244 kTestHost,
245 base::StringPrintf("%s?target=%s", kTestPath, host_ip().c_str()))));
246 EXPECT_EQ(fail_string(), watcher->WaitAndGetTitle()) << GetReason();
247 }
248
249 } // namespace
250