Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(174)

Side by Side Diff: chrome/browser/password_manager/credential_manager_browsertest.cc

Issue 2947413002: Restrict CM API interface request and message dispatch. (Closed)
Patch Set: Reworked tests, added error handler in renderer. Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/macros.h" 5 #include "base/macros.h"
6 #include "base/run_loop.h"
6 #include "base/stl_util.h" 7 #include "base/stl_util.h"
8 #include "base/strings/stringprintf.h"
7 #include "base/strings/utf_string_conversions.h" 9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
8 #include "chrome/browser/password_manager/password_manager_test_base.h" 11 #include "chrome/browser/password_manager/password_manager_test_base.h"
9 #include "chrome/browser/password_manager/password_store_factory.h" 12 #include "chrome/browser/password_manager/password_store_factory.h"
10 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/profiles/profile_io_data.h" 14 #include "chrome/browser/profiles/profile_io_data.h"
12 #include "chrome/browser/ui/browser.h" 15 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/passwords/passwords_model_delegate.h" 16 #include "chrome/browser/ui/passwords/passwords_model_delegate.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
14 #include "chrome/test/base/ui_test_utils.h" 18 #include "chrome/test/base/ui_test_utils.h"
15 #include "components/password_manager/core/browser/password_bubble_experiment.h" 19 #include "components/password_manager/core/browser/password_bubble_experiment.h"
16 #include "components/password_manager/core/browser/test_password_store.h" 20 #include "components/password_manager/core/browser/test_password_store.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/browser/web_contents_observer.h"
17 #include "content/public/test/browser_test.h" 23 #include "content/public/test/browser_test.h"
18 #include "content/public/test/browser_test_utils.h" 24 #include "content/public/test/browser_test_utils.h"
19 #include "net/dns/mock_host_resolver.h" 25 #include "net/dns/mock_host_resolver.h"
20 26
21 namespace { 27 namespace {
22 28
29 // This class implements waiting for RenderFrameHost destruction. It relies on
30 // the fact that RenderFrameDeleted event is fired when RenderFrameHost is
31 // destroyed.
vasilii 2017/06/29 11:59:59 Is the second sentence trying to say more than "Re
engedy 2017/06/30 19:41:55 Yes, there is a subtle difference between the `Del
32 class RenderFrameHostDestructionObserver : public content::WebContentsObserver {
33 public:
34 explicit RenderFrameHostDestructionObserver(content::RenderFrameHost* rfh)
35 : WebContentsObserver(content::WebContents::FromRenderFrameHost(rfh)),
36 render_frame_host_(rfh) {}
37 ~RenderFrameHostDestructionObserver() override {}
38
39 void WaitForDestruction() {
40 if (destroyed_)
41 return;
vasilii 2017/06/29 11:59:59 Note that if destroyed_ is true then run_loop_.Run
engedy 2017/06/30 19:41:55 N/A, removed this class.
42 run_loop_.Run();
43 }
44
45 // WebContentsObserver:
46 void RenderFrameDeleted(content::RenderFrameHost* rfh) override {
47 if (rfh == render_frame_host_) {
48 ASSERT_FALSE(destroyed_);
49 destroyed_ = true;
50 run_loop_.Quit();
51 }
52 }
53
54 private:
55 base::RunLoop run_loop_;
56 content::RenderFrameHost* render_frame_host_;
57
58 bool destroyed_ = false;
59
60 DISALLOW_COPY_AND_ASSIGN(RenderFrameHostDestructionObserver);
61 };
62
23 class CredentialManagerBrowserTest : public PasswordManagerBrowserTestBase { 63 class CredentialManagerBrowserTest : public PasswordManagerBrowserTestBase {
24 public: 64 public:
25 CredentialManagerBrowserTest() = default; 65 CredentialManagerBrowserTest() = default;
26 66
27 void SetUpOnMainThread() override { 67 void SetUpOnMainThread() override {
28 PasswordManagerBrowserTestBase::SetUpOnMainThread(); 68 PasswordManagerBrowserTestBase::SetUpOnMainThread();
29 // Redirect all requests to localhost. 69 // Redirect all requests to localhost.
30 host_resolver()->AddRule("*", "127.0.0.1"); 70 host_resolver()->AddRule("*", "127.0.0.1");
31 } 71 }
32 72
33 bool IsShowingAccountChooser() { 73 bool IsShowingAccountChooser() {
34 return PasswordsModelDelegateFromWebContents(WebContents())-> 74 return PasswordsModelDelegateFromWebContents(WebContents())->
35 GetState() == password_manager::ui::CREDENTIAL_REQUEST_STATE; 75 GetState() == password_manager::ui::CREDENTIAL_REQUEST_STATE;
36 } 76 }
37 77
38 // Similarly to PasswordManagerBrowserTestBase::NavigateToFile this is a 78 // Similarly to PasswordManagerBrowserTestBase::NavigateToFile this is a
39 // wrapper around ui_test_utils::NavigateURL that waits until DidFinishLoad() 79 // wrapper around ui_test_utils::NavigateURL that waits until DidFinishLoad()
40 // fires. Different to NavigateToFile this method allows passing a test_server 80 // fires. Different to NavigateToFile this method allows passing a test_server
41 // and modifications to the hostname. 81 // and modifications to the hostname.
42 void NavigateToURL(const net::EmbeddedTestServer& test_server, 82 void NavigateToURL(const net::EmbeddedTestServer& test_server,
43 const std::string& hostname, 83 const std::string& hostname,
44 const std::string& relative_url) { 84 const std::string& relative_url) {
45 NavigationObserver observer(WebContents()); 85 NavigationObserver observer(WebContents());
46 GURL url = test_server.GetURL(hostname, relative_url); 86 GURL url = test_server.GetURL(hostname, relative_url);
47 ui_test_utils::NavigateToURL(browser(), url); 87 ui_test_utils::NavigateToURL(browser(), url);
48 observer.Wait(); 88 observer.Wait();
49 } 89 }
50 90
91 // Triggers a call to `navigator.credentials.get` to retrieve passwords, waits
92 // for success, and ASSERTs that |expect_has_results| is satisfied.
93 void TriggerNavigatorGetPasswordCredentialsAndExpectHasResult(
94 content::WebContents* web_contents,
95 bool expect_has_results) {
96 bool result = false;
97 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
98 web_contents,
99 "navigator.credentials.get({password: true}).then(c => {"
100 " window.domAutomationController.send(!!c);"
101 "});",
102 &result));
103 ASSERT_EQ(expect_has_results, result);
104 }
105
106 // Schedules a call to be made to navigator.credentials.store() in the
107 // `unload` handler to save a credential with |username| and |password|.
108 void ScheduleNavigatorStoreCredentialAtUnload(
109 content::WebContents* web_contents,
110 const char* username,
111 const char* password) {
112 ASSERT_TRUE(content::ExecuteScript(
113 web_contents,
114 base::StringPrintf(
115 "window.addEventListener(\"unload\", () => {"
116 " var c = new PasswordCredential({ id: '%s', password: '%s' });"
117 " navigator.credentials.store(c);"
118 "});",
119 username, password)));
120 }
121
122 content::WebContents* active_web_contents() {
123 return browser()->tab_strip_model()->GetActiveWebContents();
124 }
125
51 private: 126 private:
52 DISALLOW_COPY_AND_ASSIGN(CredentialManagerBrowserTest); 127 DISALLOW_COPY_AND_ASSIGN(CredentialManagerBrowserTest);
53 }; 128 };
54 129
55 // Tests. 130 // Tests.
56 131
57 IN_PROC_BROWSER_TEST_F(CredentialManagerBrowserTest, 132 IN_PROC_BROWSER_TEST_F(CredentialManagerBrowserTest,
58 AccountChooserWithOldCredentialAndNavigation) { 133 AccountChooserWithOldCredentialAndNavigation) {
59 // Save credentials with 'skip_zero_click'. 134 // Save credentials with 'skip_zero_click'.
60 scoped_refptr<password_manager::TestPasswordStore> password_store = 135 scoped_refptr<password_manager::TestPasswordStore> password_store =
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after
238 NavigationObserver observer(WebContents()); 313 NavigationObserver observer(WebContents());
239 observer.SetPathToWaitFor("/password/done.html"); 314 observer.SetPathToWaitFor("/password/done.html");
240 observer.Wait(); 315 observer.Wait();
241 316
242 BubbleObserver prompt_observer(WebContents()); 317 BubbleObserver prompt_observer(WebContents());
243 // The autofill password manager shouldn't react to the successful login 318 // The autofill password manager shouldn't react to the successful login
244 // because it was suppressed when the site got the credential back. 319 // because it was suppressed when the site got the credential back.
245 EXPECT_FALSE(prompt_observer.IsShowingSavePrompt()); 320 EXPECT_FALSE(prompt_observer.IsShowingSavePrompt());
246 } 321 }
247 322
323 // Regression test for https://crbug.com/736357.
324 IN_PROC_BROWSER_TEST_F(
325 CredentialManagerBrowserTest,
326 StoreInUnloadHandler_ServicedUsingPriorURLForSameSiteNavigation) {
327 // Use URLs that differ on subdomains so we can tell which one was used for
328 // saving, but they still belong to the same SiteInstance, so they will be
329 // renderered in the same process.
330 const GURL a_url1 = https_test_server().GetURL("foo.a.com", "/title1.html");
331 const GURL a_url2 = https_test_server().GetURL("bar.a.com", "/title2.html");
332
333 for (const auto preestablish_mojo_pipe : {false, true}) {
vasilii 2017/06/29 11:59:59 I'd prefer to split this test into two. The body c
vasilii 2017/06/29 12:00:00 const bool
engedy 2017/06/30 19:41:55 Done.
engedy 2017/06/30 19:41:55 Ack. Done this one, and also the one below.
334 SCOPED_TRACE(::testing::Message()
335 << "Pre-establish Mojo pipe ahead of time: "
336 << preestablish_mojo_pipe);
337
338 // Open a new WebContents, navigate to a mostly empty page.
339 NavigateToURLWithDisposition(
340 browser(), a_url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
341 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
342
343 ChromePasswordManagerClient* client =
344 ChromePasswordManagerClient::FromWebContents(active_web_contents());
345
346 // Optionally set up a Mojo connection to the CredentialManagerImpl ahead of
347 // time, instead of letting it be established on-demand when the call to
348 // store() triggered from the unload handler.
349 if (preestablish_mojo_pipe) {
350 EXPECT_FALSE(client->has_binding_for_credential_manager());
351 ASSERT_NO_FATAL_FAILURE(
352 TriggerNavigatorGetPasswordCredentialsAndExpectHasResult(
353 active_web_contents(), false));
354 EXPECT_TRUE(client->has_binding_for_credential_manager());
355 }
356
357 // Schedule storing a credential on the `unload` event.
358 ASSERT_NO_FATAL_FAILURE(ScheduleNavigatorStoreCredentialAtUnload(
359 active_web_contents(), "user", "hunter2"));
360
361 // Trigger a same-site navigation that is carried out in the same renderer.
362 content::RenderFrameHost* old_rfh = active_web_contents()->GetMainFrame();
363 ui_test_utils::NavigateToURL(browser(), a_url2);
364 base::RunLoop().RunUntilIdle();
vasilii 2017/06/29 11:59:59 I'm not a big fan of RunUntilIdle() in the browser
engedy 2017/06/30 19:41:55 Done.
365 ASSERT_EQ(old_rfh, active_web_contents()->GetMainFrame());
366
367 // Ensure that the old document no longer has a Mojo connection to the
368 // CredentialManagerImpl, nor can it get one later.
369 //
370 // Because the InterfaceRegistry through which the interface to the
371 // CredentialManagerImpl is retrieved is synchronized to FrameHost
vasilii 2017/06/29 11:59:59 Is this request itself coming from IPC?
engedy 2017/06/30 19:41:54 As discussed offline, made this an associated inte
372 // messages, there cannot be InterfaceRequest messages in flight.
373 //
374 // Furthermore, FrameLoader::PrepareForCommit had already shut down the old
375 // Document before the provisional load could be commited in the renderer,
376 // so no new InterfaceRequests can be issued either.
377 //
378 // Hence it is sufficient to check that the Mojo connection is closed now.
379 EXPECT_FALSE(client->has_binding_for_credential_manager());
vasilii 2017/06/29 11:59:59 Is it the check that would fail before the CL?
engedy 2017/06/30 19:41:55 Among others, yes.
380
381 // Ensure that the navigator.credentials.store() call was either never
382 // serviced, or was serviced in the context of the old URL, |a_url|.
383 //
384 // Both are possible, as CredentialManager Mojo messages on an pre-existing
385 // connection are no longer synchronized to FrameHost messages. Therefore,
386 // servicing the store() called from the `unload` handler, triggered from
387 // FrameLoader::PrepareForCommit, is at race with DidFinishNavigation, which
388 // is triggered by FrameHostMsg DidCommitProvisionalLoad.
389 if (client->was_store_ever_called()) {
390 BubbleObserver prompt_observer(active_web_contents());
391 prompt_observer.WaitForSavePrompt();
vasilii 2017/06/29 11:59:59 You may really crash here. WaitForSavePrompt() ca
engedy 2017/06/30 19:41:54 Done. (Using WebContents() now.)
392 ASSERT_TRUE(prompt_observer.IsShowingSavePrompt());
393 prompt_observer.AcceptSavePrompt();
394
395 WaitForPasswordStore();
396
397 password_manager::TestPasswordStore* test_password_store =
398 static_cast<password_manager::TestPasswordStore*>(
399 PasswordStoreFactory::GetForProfile(
400 browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS)
401 .get());
402
403 ASSERT_EQ(1u, test_password_store->stored_passwords().size());
404 autofill::PasswordForm signin_form =
405 test_password_store->stored_passwords().begin()->second[0];
406 EXPECT_EQ(base::ASCIIToUTF16("user"), signin_form.username_value);
407 EXPECT_EQ(base::ASCIIToUTF16("hunter2"), signin_form.password_value);
408 EXPECT_EQ(a_url1.GetOrigin(), signin_form.origin);
409 test_password_store->Clear();
410 }
411 }
412 }
413
414 // Regression test for https://crbug.com/736357.
415 IN_PROC_BROWSER_TEST_F(CredentialManagerBrowserTest,
416 StoreInUnloadHandler_IgnoredOnCrossSiteNavigation) {
417 const GURL a_url = https_test_server().GetURL("a.com", "/title1.html");
418 const GURL b_url = https_test_server().GetURL("b.com", "/title2.html");
419
420 for (const auto preestablish_mojo_pipe : {false, true}) {
421 SCOPED_TRACE(::testing::Message()
422 << "Pre-establish Mojo pipe ahead of time: "
423 << preestablish_mojo_pipe);
424
425 // Open a new WebContents, navigate to a mostly empty page.
426 NavigateToURLWithDisposition(
427 browser(), a_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
428 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
429
430 ChromePasswordManagerClient* client =
431 ChromePasswordManagerClient::FromWebContents(active_web_contents());
432
433 // Optionally set up a Mojo connection to the CredentialManagerImpl ahead of
434 // time, instead of letting it be established on-demand when the call to
435 // store() triggered from the unload handler.
436 if (preestablish_mojo_pipe) {
437 EXPECT_FALSE(client->has_binding_for_credential_manager());
438 ASSERT_NO_FATAL_FAILURE(
439 TriggerNavigatorGetPasswordCredentialsAndExpectHasResult(
440 active_web_contents(), false));
441 EXPECT_TRUE(client->has_binding_for_credential_manager());
442 }
443
444 // Schedule storing a credential on the `unload` event.
445 ASSERT_NO_FATAL_FAILURE(ScheduleNavigatorStoreCredentialAtUnload(
446 active_web_contents(), "user", "hunter2"));
447
448 // Trigger a cross-site navigation that is carried out in a new renderer,
449 // and which will swap out the old RenderFrameHost.
450 RenderFrameHostDestructionObserver rfh_destruction_observer(
451 active_web_contents()->GetMainFrame());
452 ui_test_utils::NavigateToURL(browser(), b_url);
453
454 // Ensure that the navigator.credentials.store() call is never serviced.
vasilii 2017/06/29 11:59:59 Why can't the same scenario as in the previous tes
engedy 2017/06/30 19:41:55 It's channel is disconnected in DidFinishNavigatio
455 // The sufficient conditions for this are:
456 // -- The swapped out RFH is destroyed, so the RenderFrame cannot establish
457 // a new Mojo connection to CredentialManagerImpl anymore.
458 // -- There is no already existing Mojo connection to CredentialManagerImpl
459 // either, which could be used to call store() in the future.
460 // -- There have not been any calls to store() in the past.
461 rfh_destruction_observer.WaitForDestruction();
462 base::RunLoop().RunUntilIdle();
vasilii 2017/06/29 11:59:59 Unnecessary I think.
engedy 2017/06/30 19:41:55 Done.
463 EXPECT_FALSE(client->has_binding_for_credential_manager());
464 EXPECT_FALSE(client->was_store_ever_called());
465 }
466 }
467
468 // Regression test for https://crbug.com/736357.
469 IN_PROC_BROWSER_TEST_F(CredentialManagerBrowserTest,
470 MojoConnectionRecreatedAfterNavigation) {
471 const GURL a_url1 = https_test_server().GetURL("a.com", "/title1.html");
472 const GURL a_url2 = https_test_server().GetURL("a.com", "/title2.html");
473 const GURL a_url2_ref = https_test_server().GetURL("a.com", "/title2.html#r");
474 const GURL b_url = https_test_server().GetURL("b.com", "/title2.html#ref");
475
476 // Enable 'auto signin' for the profile.
477 password_bubble_experiment::RecordAutoSignInPromptFirstRunExperienceWasShown(
478 browser()->profile()->GetPrefs());
479
480 // Navigate to a mostly empty page.
481 ui_test_utils::NavigateToURL(browser(), a_url1);
482
483 ChromePasswordManagerClient* client =
484 ChromePasswordManagerClient::FromWebContents(active_web_contents());
485
486 // Store a credential, and expect it to establish the Mojo connection.
487 EXPECT_FALSE(client->has_binding_for_credential_manager());
488 EXPECT_FALSE(client->was_store_ever_called());
489
490 ASSERT_TRUE(content::ExecuteScript(
491 active_web_contents(),
492 "var c = new PasswordCredential({ id: 'user', password: 'hunter2' });"
493 "navigator.credentials.store(c);"));
494
495 BubbleObserver prompt_observer(active_web_contents());
vasilii 2017/06/29 11:59:59 Please use WebContents() for the reasons stated ab
engedy 2017/06/30 19:41:55 Done.
496 prompt_observer.WaitForSavePrompt();
497 ASSERT_TRUE(prompt_observer.IsShowingSavePrompt());
498 prompt_observer.AcceptSavePrompt();
499 WaitForPasswordStore();
500
501 EXPECT_TRUE(client->has_binding_for_credential_manager());
502 EXPECT_TRUE(client->was_store_ever_called());
503
504 // Trigger a same-site navigation.
505 content::RenderFrameHost* old_rfh = active_web_contents()->GetMainFrame();
506 ui_test_utils::NavigateToURL(browser(), a_url2);
507 base::RunLoop().RunUntilIdle();
vasilii 2017/06/29 12:00:00 ditto
engedy 2017/06/30 19:41:55 Done.
508 ASSERT_EQ(old_rfh, active_web_contents()->GetMainFrame());
509
510 // Expect the Mojo connection closed.
511 EXPECT_FALSE(client->has_binding_for_credential_manager());
512
513 // Calling navigator.credentials.get() again should re-establish the Mojo
514 // connection and succeed.
515 ASSERT_NO_FATAL_FAILURE(
516 TriggerNavigatorGetPasswordCredentialsAndExpectHasResult(
517 active_web_contents(), true));
518 EXPECT_TRUE(client->has_binding_for_credential_manager());
519
520 // Same-document navigation. Call to get() succeeds.
521 ui_test_utils::NavigateToURL(browser(), a_url2_ref);
522 ASSERT_EQ(old_rfh, active_web_contents()->GetMainFrame());
vasilii 2017/06/29 12:00:00 Do you want to check the connection?
engedy 2017/06/30 19:41:55 Done.
523 ASSERT_NO_FATAL_FAILURE(
524 TriggerNavigatorGetPasswordCredentialsAndExpectHasResult(
525 active_web_contents(), true));
526
527 // Cross-site navigation. Call to get() succeeds without results.
528 ui_test_utils::NavigateToURL(browser(), b_url);
529 ASSERT_NO_FATAL_FAILURE(
530 TriggerNavigatorGetPasswordCredentialsAndExpectHasResult(
531 active_web_contents(), false));
532
533 // Trigger a cross-site navigation back. Call to get() should still succeed,
534 // and once again with results.
535 ui_test_utils::NavigateToURL(browser(), a_url1);
536 ASSERT_NO_FATAL_FAILURE(
537 TriggerNavigatorGetPasswordCredentialsAndExpectHasResult(
538 active_web_contents(), true));
539 }
540
248 IN_PROC_BROWSER_TEST_F(CredentialManagerBrowserTest, SaveViaAPIAndAutofill) { 541 IN_PROC_BROWSER_TEST_F(CredentialManagerBrowserTest, SaveViaAPIAndAutofill) {
249 NavigateToFile("/password/password_form.html"); 542 NavigateToFile("/password/password_form.html");
250 543
251 ASSERT_TRUE(content::ExecuteScript( 544 ASSERT_TRUE(content::ExecuteScript(
252 RenderViewHost(), 545 RenderViewHost(),
253 "document.getElementById('input_submit_button').addEventListener('click'," 546 "document.getElementById('input_submit_button').addEventListener('click',"
254 "function(event) {" 547 "function(event) {"
255 "var c = new PasswordCredential({ id: 'user', password: 'API' });" 548 "var c = new PasswordCredential({ id: 'user', password: 'API' });"
256 "navigator.credentials.store(c);" 549 "navigator.credentials.store(c);"
257 "});")); 550 "});"));
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
349 642
350 // Reload the page and make sure it's autofilled. 643 // Reload the page and make sure it's autofilled.
351 NavigateToFile("/password/password_form.html"); 644 NavigateToFile("/password/password_form.html");
352 WaitForElementValue("username_field", "user"); 645 WaitForElementValue("username_field", "user");
353 content::SimulateMouseClickAt( 646 content::SimulateMouseClickAt(
354 WebContents(), 0, blink::WebMouseEvent::Button::kLeft, gfx::Point(1, 1)); 647 WebContents(), 0, blink::WebMouseEvent::Button::kLeft, gfx::Point(1, 1));
355 WaitForElementValue("password_field", "12345"); 648 WaitForElementValue("password_field", "12345");
356 } 649 }
357 650
358 } // namespace 651 } // namespace
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698