Home | History | Annotate | Download | only in passwords
      1 // Copyright 2014 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 "base/prefs/pref_service.h"
      6 #include "base/strings/utf_string_conversions.h"
      7 #include "base/time/time.h"
      8 #include "chrome/browser/ui/passwords/manage_passwords_bubble.h"
      9 #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
     10 #include "chrome/browser/ui/passwords/manage_passwords_icon.h"
     11 #include "chrome/browser/ui/passwords/manage_passwords_icon_mock.h"
     12 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller_mock.h"
     13 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
     14 #include "chrome/test/base/testing_profile.h"
     15 #include "components/autofill/core/common/password_form.h"
     16 #include "components/password_manager/core/browser/password_form_manager.h"
     17 #include "components/password_manager/core/browser/stub_password_manager_client.h"
     18 #include "components/password_manager/core/browser/stub_password_manager_driver.h"
     19 #include "components/password_manager/core/common/password_manager_ui.h"
     20 #include "content/public/test/test_browser_thread_bundle.h"
     21 #include "content/public/test/web_contents_tester.h"
     22 #include "testing/gmock/include/gmock/gmock.h"
     23 #include "testing/gtest/include/gtest/gtest.h"
     24 
     25 namespace {
     26 
     27 const int64 kSlowNavigationDelayInMS = 2000;
     28 const int64 kQuickNavigationDelayInMS = 500;
     29 
     30 class MockElapsedTimer : public base::ElapsedTimer {
     31  public:
     32   MockElapsedTimer() {}
     33   virtual base::TimeDelta Elapsed() const OVERRIDE { return delta_; }
     34 
     35   void Advance(int64 ms) { delta_ = base::TimeDelta::FromMilliseconds(ms); }
     36 
     37  private:
     38   base::TimeDelta delta_;
     39 
     40   DISALLOW_COPY_AND_ASSIGN(MockElapsedTimer);
     41 };
     42 
     43 }  // namespace
     44 
     45 class ManagePasswordsUIControllerTest : public ChromeRenderViewHostTestHarness {
     46  public:
     47   ManagePasswordsUIControllerTest() {}
     48 
     49   virtual void SetUp() OVERRIDE {
     50     ChromeRenderViewHostTestHarness::SetUp();
     51 
     52     // Create the test UIController here so that it's bound to
     53     // |test_web_contents_|, and will be retrieved correctly via
     54     // ManagePasswordsUIController::FromWebContents in |controller()|.
     55     new ManagePasswordsUIControllerMock(web_contents());
     56 
     57     test_form_.origin = GURL("http://example.com");
     58 
     59     // We need to be on a "webby" URL for most tests.
     60     content::WebContentsTester::For(web_contents())
     61         ->NavigateAndCommit(GURL("http://example.com"));
     62   }
     63 
     64   autofill::PasswordForm& test_form() { return test_form_; }
     65 
     66   ManagePasswordsUIControllerMock* controller() {
     67     return static_cast<ManagePasswordsUIControllerMock*>(
     68         ManagePasswordsUIController::FromWebContents(web_contents()));
     69   }
     70 
     71  private:
     72   autofill::PasswordForm test_form_;
     73 };
     74 
     75 TEST_F(ManagePasswordsUIControllerTest, DefaultState) {
     76   EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->state());
     77   EXPECT_FALSE(controller()->PasswordPendingUserDecision());
     78   EXPECT_EQ(GURL::EmptyGURL(), controller()->origin());
     79 
     80   ManagePasswordsIconMock mock;
     81   controller()->UpdateIconAndBubbleState(&mock);
     82   EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state());
     83 }
     84 
     85 TEST_F(ManagePasswordsUIControllerTest, PasswordAutofilled) {
     86   base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
     87   autofill::PasswordFormMap map;
     88   map[kTestUsername] = &test_form();
     89   controller()->OnPasswordAutofilled(map);
     90 
     91   EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state());
     92   EXPECT_FALSE(controller()->PasswordPendingUserDecision());
     93   EXPECT_EQ(test_form().origin, controller()->origin());
     94   EXPECT_EQ(1u, controller()->best_matches().size());
     95   ASSERT_EQ(1u, controller()->best_matches().count(kTestUsername));
     96 
     97   // Controller should store a separate copy of the form as it doesn't own it.
     98   EXPECT_NE(&test_form(),
     99             controller()->best_matches().find(kTestUsername)->second);
    100 
    101   ManagePasswordsIconMock mock;
    102   controller()->UpdateIconAndBubbleState(&mock);
    103   EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state());
    104 }
    105 
    106 TEST_F(ManagePasswordsUIControllerTest, PasswordSubmitted) {
    107   password_manager::StubPasswordManagerClient client;
    108   password_manager::StubPasswordManagerDriver driver;
    109   scoped_ptr<password_manager::PasswordFormManager> test_form_manager(
    110       new password_manager::PasswordFormManager(
    111           NULL, &client, &driver, test_form(), false));
    112   controller()->OnPasswordSubmitted(test_form_manager.Pass());
    113   EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE,
    114             controller()->state());
    115   EXPECT_TRUE(controller()->PasswordPendingUserDecision());
    116 
    117   // TODO(mkwst): This should be the value of test_form().origin, but
    118   // it's being masked by the stub implementation of
    119   // ManagePasswordsUIControllerMock::PendingCredentials.
    120   EXPECT_EQ(GURL::EmptyGURL(), controller()->origin());
    121 
    122   ManagePasswordsIconMock mock;
    123   controller()->UpdateIconAndBubbleState(&mock);
    124   EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE,
    125             mock.state());
    126 }
    127 
    128 TEST_F(ManagePasswordsUIControllerTest, PasswordSaved) {
    129   password_manager::StubPasswordManagerClient client;
    130   password_manager::StubPasswordManagerDriver driver;
    131   scoped_ptr<password_manager::PasswordFormManager> test_form_manager(
    132       new password_manager::PasswordFormManager(
    133           NULL, &client, &driver, test_form(), false));
    134   controller()->OnPasswordSubmitted(test_form_manager.Pass());
    135 
    136   ManagePasswordsIconMock mock;
    137   controller()->UpdateIconAndBubbleState(&mock);
    138   controller()->SavePassword();
    139   controller()->UpdateIconAndBubbleState(&mock);
    140   EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state());
    141 }
    142 
    143 TEST_F(ManagePasswordsUIControllerTest, PasswordBlacklisted) {
    144   password_manager::StubPasswordManagerClient client;
    145   password_manager::StubPasswordManagerDriver driver;
    146   scoped_ptr<password_manager::PasswordFormManager> test_form_manager(
    147       new password_manager::PasswordFormManager(
    148           NULL, &client, &driver, test_form(), false));
    149   controller()->OnPasswordSubmitted(test_form_manager.Pass());
    150 
    151   ManagePasswordsIconMock mock;
    152   controller()->UpdateIconAndBubbleState(&mock);
    153   controller()->NeverSavePassword();
    154   controller()->UpdateIconAndBubbleState(&mock);
    155   EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state());
    156 }
    157 
    158 TEST_F(ManagePasswordsUIControllerTest, QuickNavigations) {
    159   password_manager::StubPasswordManagerClient client;
    160   password_manager::StubPasswordManagerDriver driver;
    161   scoped_ptr<password_manager::PasswordFormManager> test_form_manager(
    162       new password_manager::PasswordFormManager(
    163           NULL, &client, &driver, test_form(), false));
    164   controller()->OnPasswordSubmitted(test_form_manager.Pass());
    165   ManagePasswordsIconMock mock;
    166   controller()->UpdateIconAndBubbleState(&mock);
    167   EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE,
    168             mock.state());
    169 
    170   // Fake-navigate within a second. We expect the bubble's state to persist
    171   // if a navigation occurs too quickly for a user to reasonably have been
    172   // able to interact with the bubble. This happens on `accounts.google.com`,
    173   // for instance.
    174   scoped_ptr<MockElapsedTimer> timer(new MockElapsedTimer());
    175   timer->Advance(kQuickNavigationDelayInMS);
    176   controller()->SetTimer(timer.release());
    177   controller()->DidNavigateMainFrame(content::LoadCommittedDetails(),
    178                                      content::FrameNavigateParams());
    179   controller()->UpdateIconAndBubbleState(&mock);
    180 
    181   EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE,
    182             mock.state());
    183 }
    184 
    185 TEST_F(ManagePasswordsUIControllerTest, SlowNavigations) {
    186   password_manager::StubPasswordManagerClient client;
    187   password_manager::StubPasswordManagerDriver driver;
    188   scoped_ptr<password_manager::PasswordFormManager> test_form_manager(
    189       new password_manager::PasswordFormManager(
    190           NULL, &client, &driver, test_form(), false));
    191   controller()->OnPasswordSubmitted(test_form_manager.Pass());
    192   ManagePasswordsIconMock mock;
    193   controller()->UpdateIconAndBubbleState(&mock);
    194   EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE,
    195             mock.state());
    196 
    197   // Fake-navigate after a second. We expect the bubble's state to be reset
    198   // if a navigation occurs after this limit.
    199   scoped_ptr<MockElapsedTimer> timer(new MockElapsedTimer());
    200   timer->Advance(kSlowNavigationDelayInMS);
    201   controller()->SetTimer(timer.release());
    202   controller()->DidNavigateMainFrame(content::LoadCommittedDetails(),
    203                                      content::FrameNavigateParams());
    204   controller()->UpdateIconAndBubbleState(&mock);
    205 
    206   EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state());
    207 }
    208 
    209 TEST_F(ManagePasswordsUIControllerTest, PasswordSubmittedToNonWebbyURL) {
    210   // Navigate to a non-webby URL, then see what happens!
    211   content::WebContentsTester::For(web_contents())
    212       ->NavigateAndCommit(GURL("chrome://sign-in"));
    213 
    214   password_manager::StubPasswordManagerClient client;
    215   password_manager::StubPasswordManagerDriver driver;
    216   scoped_ptr<password_manager::PasswordFormManager> test_form_manager(
    217       new password_manager::PasswordFormManager(
    218           NULL, &client, &driver, test_form(), false));
    219   controller()->OnPasswordSubmitted(test_form_manager.Pass());
    220   EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->state());
    221   EXPECT_FALSE(controller()->PasswordPendingUserDecision());
    222 
    223   // TODO(mkwst): This should be the value of test_form().origin, but
    224   // it's being masked by the stub implementation of
    225   // ManagePasswordsUIControllerMock::PendingCredentials.
    226   EXPECT_EQ(GURL::EmptyGURL(), controller()->origin());
    227 
    228   ManagePasswordsIconMock mock;
    229   controller()->UpdateIconAndBubbleState(&mock);
    230   EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state());
    231 }
    232 
    233 TEST_F(ManagePasswordsUIControllerTest, BlacklistBlockedAutofill) {
    234   test_form().blacklisted_by_user = true;
    235   base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
    236   autofill::PasswordFormMap map;
    237   map[kTestUsername] = &test_form();
    238   controller()->OnBlacklistBlockedAutofill(map);
    239 
    240   EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, controller()->state());
    241   EXPECT_FALSE(controller()->PasswordPendingUserDecision());
    242   EXPECT_EQ(test_form().origin, controller()->origin());
    243   EXPECT_EQ(1u, controller()->best_matches().size());
    244   ASSERT_EQ(1u, controller()->best_matches().count(kTestUsername));
    245 
    246   // Controller should store a separate copy of the form as it doesn't own it.
    247   EXPECT_NE(&test_form(),
    248             controller()->best_matches().find(kTestUsername)->second);
    249 
    250   ManagePasswordsIconMock mock;
    251   controller()->UpdateIconAndBubbleState(&mock);
    252   EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state());
    253 }
    254 
    255 TEST_F(ManagePasswordsUIControllerTest, ClickedUnblacklist) {
    256   base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
    257   autofill::PasswordFormMap map;
    258   map[kTestUsername] = &test_form();
    259   controller()->OnBlacklistBlockedAutofill(map);
    260   controller()->UnblacklistSite();
    261 
    262   EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state());
    263   EXPECT_FALSE(controller()->PasswordPendingUserDecision());
    264   EXPECT_EQ(test_form().origin, controller()->origin());
    265 
    266   ManagePasswordsIconMock mock;
    267   controller()->UpdateIconAndBubbleState(&mock);
    268   EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state());
    269 }
    270 
    271 TEST_F(ManagePasswordsUIControllerTest, UnblacklistedElsewhere) {
    272   test_form().blacklisted_by_user = true;
    273   base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
    274   autofill::PasswordFormMap map;
    275   map[kTestUsername] = &test_form();
    276   controller()->OnBlacklistBlockedAutofill(map);
    277 
    278   password_manager::PasswordStoreChange change(
    279       password_manager::PasswordStoreChange::REMOVE, test_form());
    280   password_manager::PasswordStoreChangeList list(1, change);
    281   controller()->OnLoginsChanged(list);
    282 
    283   EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state());
    284   EXPECT_FALSE(controller()->PasswordPendingUserDecision());
    285   EXPECT_EQ(test_form().origin, controller()->origin());
    286 
    287   ManagePasswordsIconMock mock;
    288   controller()->UpdateIconAndBubbleState(&mock);
    289   EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state());
    290 }
    291 
    292 TEST_F(ManagePasswordsUIControllerTest, BlacklistedElsewhere) {
    293   base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
    294   autofill::PasswordFormMap map;
    295   map[kTestUsername] = &test_form();
    296   controller()->OnPasswordAutofilled(map);
    297 
    298   test_form().blacklisted_by_user = true;
    299   password_manager::PasswordStoreChange change(
    300       password_manager::PasswordStoreChange::ADD, test_form());
    301   password_manager::PasswordStoreChangeList list(1, change);
    302   controller()->OnLoginsChanged(list);
    303 
    304   EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, controller()->state());
    305   EXPECT_FALSE(controller()->PasswordPendingUserDecision());
    306   EXPECT_EQ(test_form().origin, controller()->origin());
    307 
    308   ManagePasswordsIconMock mock;
    309   controller()->UpdateIconAndBubbleState(&mock);
    310   EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state());
    311 }
    312 
    313 TEST_F(ManagePasswordsUIControllerTest, AutomaticPasswordSave) {
    314   password_manager::StubPasswordManagerClient client;
    315   password_manager::StubPasswordManagerDriver driver;
    316   scoped_ptr<password_manager::PasswordFormManager> test_form_manager(
    317       new password_manager::PasswordFormManager(
    318           NULL, &client, &driver, test_form(), false));
    319 
    320   controller()->OnAutomaticPasswordSave(test_form_manager.Pass());
    321   EXPECT_EQ(password_manager::ui::CONFIRMATION_STATE, controller()->state());
    322 
    323   ManagePasswordsIconMock mock;
    324   controller()->UpdateIconAndBubbleState(&mock);
    325   EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state());
    326 }
    327