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