1 // Copyright (c) 2012 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/basictypes.h" 6 #include "base/bind.h" 7 #include "base/stl_util.h" 8 #include "base/strings/string_util.h" 9 #include "base/synchronization/waitable_event.h" 10 #include "base/time/time.h" 11 #include "chrome/browser/chrome_notification_types.h" 12 #include "chrome/browser/password_manager/password_form_data.h" 13 #include "chrome/browser/password_manager/password_store_consumer.h" 14 #include "chrome/browser/password_manager/password_store_default.h" 15 #include "chrome/test/base/testing_profile.h" 16 #include "content/public/browser/notification_details.h" 17 #include "content/public/browser/notification_registrar.h" 18 #include "content/public/browser/notification_source.h" 19 #include "content/public/test/mock_notification_observer.h" 20 #include "content/public/test/test_browser_thread.h" 21 #include "testing/gmock/include/gmock/gmock.h" 22 #include "testing/gtest/include/gtest/gtest.h" 23 24 using base::WaitableEvent; 25 using content::BrowserThread; 26 using testing::_; 27 using testing::DoAll; 28 using testing::WithArg; 29 using content::PasswordForm; 30 31 namespace { 32 33 class MockPasswordStoreConsumer : public PasswordStoreConsumer { 34 public: 35 MOCK_METHOD2(OnPasswordStoreRequestDone, 36 void(CancelableRequestProvider::Handle, 37 const std::vector<PasswordForm*>&)); 38 MOCK_METHOD1(OnGetPasswordStoreResults, 39 void(const std::vector<PasswordForm*>&)); 40 }; 41 42 // This class will add and remove a mock notification observer from 43 // the DB thread. 44 class DBThreadObserverHelper 45 : public base::RefCountedThreadSafe<DBThreadObserverHelper, 46 BrowserThread::DeleteOnDBThread> { 47 public: 48 DBThreadObserverHelper() : done_event_(true, false) {} 49 50 void Init(PasswordStore* password_store) { 51 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 52 BrowserThread::PostTask( 53 BrowserThread::DB, 54 FROM_HERE, 55 base::Bind(&DBThreadObserverHelper::AddObserverTask, 56 this, 57 make_scoped_refptr(password_store))); 58 done_event_.Wait(); 59 } 60 61 content::MockNotificationObserver& observer() { 62 return observer_; 63 } 64 65 protected: 66 virtual ~DBThreadObserverHelper() { 67 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 68 registrar_.RemoveAll(); 69 } 70 71 void AddObserverTask(PasswordStore* password_store) { 72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 73 registrar_.Add(&observer_, 74 chrome::NOTIFICATION_LOGINS_CHANGED, 75 content::Source<PasswordStore>(password_store)); 76 done_event_.Signal(); 77 } 78 79 WaitableEvent done_event_; 80 content::NotificationRegistrar registrar_; 81 content::MockNotificationObserver observer_; 82 83 private: 84 friend struct BrowserThread::DeleteOnThread<BrowserThread::DB>; 85 friend class base::DeleteHelper<DBThreadObserverHelper>; 86 }; 87 88 } // anonymous namespace 89 90 class PasswordStoreTest : public testing::Test { 91 protected: 92 PasswordStoreTest() 93 : ui_thread_(BrowserThread::UI, &message_loop_), 94 db_thread_(BrowserThread::DB) { 95 } 96 97 virtual void SetUp() { 98 ASSERT_TRUE(db_thread_.Start()); 99 100 profile_.reset(new TestingProfile()); 101 102 login_db_.reset(new LoginDatabase()); 103 ASSERT_TRUE(login_db_->Init(profile_->GetPath().Append( 104 FILE_PATH_LITERAL("login_test")))); 105 } 106 107 virtual void TearDown() { 108 db_thread_.Stop(); 109 base::MessageLoop::current()->PostTask(FROM_HERE, 110 base::MessageLoop::QuitClosure()); 111 base::MessageLoop::current()->Run(); 112 } 113 114 base::MessageLoopForUI message_loop_; 115 content::TestBrowserThread ui_thread_; 116 // PasswordStore schedules work on this thread. 117 content::TestBrowserThread db_thread_; 118 119 scoped_ptr<LoginDatabase> login_db_; 120 scoped_ptr<TestingProfile> profile_; 121 }; 122 123 ACTION(STLDeleteElements0) { 124 STLDeleteContainerPointers(arg0.begin(), arg0.end()); 125 } 126 127 ACTION(QuitUIMessageLoop) { 128 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 129 base::MessageLoop::current()->Quit(); 130 } 131 132 TEST_F(PasswordStoreTest, IgnoreOldWwwGoogleLogins) { 133 scoped_refptr<PasswordStoreDefault> store( 134 new PasswordStoreDefault(login_db_.release(), profile_.get())); 135 store->Init(); 136 137 const time_t cutoff = 1325376000; // 00:00 Jan 1 2012 UTC 138 // The passwords are all empty because PasswordStoreDefault doesn't store the 139 // actual passwords on OS X (they're stored in the Keychain instead). We could 140 // special-case it, but it's easier to just have empty passwords. 141 static const PasswordFormData form_data[] = { 142 // A form on https://www.google.com/ older than the cutoff. Will be ignored. 143 { PasswordForm::SCHEME_HTML, 144 "https://www.google.com", 145 "https://www.google.com/origin", 146 "https://www.google.com/action", 147 L"submit_element", 148 L"username_element", 149 L"password_element", 150 L"username_value_1", 151 L"", 152 true, true, cutoff - 1 }, 153 // A form on https://www.google.com/ older than the cutoff. Will be ignored. 154 { PasswordForm::SCHEME_HTML, 155 "https://www.google.com", 156 "https://www.google.com/origin", 157 "https://www.google.com/action", 158 L"submit_element", 159 L"username_element", 160 L"password_element", 161 L"username_value_2", 162 L"", 163 true, true, cutoff - 1 }, 164 // A form on https://www.google.com/ newer than the cutoff. 165 { PasswordForm::SCHEME_HTML, 166 "https://www.google.com", 167 "https://www.google.com/origin", 168 "https://www.google.com/action", 169 L"submit_element", 170 L"username_element", 171 L"password_element", 172 L"username_value_3", 173 L"", 174 true, true, cutoff + 1 }, 175 // A form on https://accounts.google.com/ older than the cutoff. 176 { PasswordForm::SCHEME_HTML, 177 "https://accounts.google.com", 178 "https://accounts.google.com/origin", 179 "https://accounts.google.com/action", 180 L"submit_element", 181 L"username_element", 182 L"password_element", 183 L"username_value", 184 L"", 185 true, true, cutoff - 1 }, 186 // A form on http://bar.example.com/ older than the cutoff. 187 { PasswordForm::SCHEME_HTML, 188 "http://bar.example.com", 189 "http://bar.example.com/origin", 190 "http://bar.example.com/action", 191 L"submit_element", 192 L"username_element", 193 L"password_element", 194 L"username_value", 195 L"", 196 true, false, cutoff - 1 }, 197 }; 198 199 // Build the forms vector and add the forms to the store. 200 std::vector<PasswordForm*> all_forms; 201 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(form_data); ++i) { 202 PasswordForm* form = CreatePasswordFormFromData(form_data[i]); 203 all_forms.push_back(form); 204 store->AddLogin(*form); 205 } 206 207 // The PasswordStore schedules tasks to run on the DB thread so we schedule 208 // yet another task to notify us that it's safe to carry on with the test. 209 // The PasswordStore doesn't really understand that it's "done" once the tasks 210 // we posted above have completed, so there's no formal notification for that. 211 WaitableEvent done(false, false); 212 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 213 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 214 done.Wait(); 215 216 // We expect to get back only the "recent" www.google.com login. 217 // Theoretically these should never actually exist since there are no longer 218 // any login forms on www.google.com to save, but we technically allow them. 219 // We should not get back the older saved password though. 220 PasswordForm www_google; 221 www_google.scheme = PasswordForm::SCHEME_HTML; 222 www_google.signon_realm = "https://www.google.com"; 223 std::vector<PasswordForm*> www_google_expected; 224 www_google_expected.push_back(all_forms[2]); 225 226 // We should still get the accounts.google.com login even though it's older 227 // than our cutoff - this is the new location of all Google login forms. 228 PasswordForm accounts_google; 229 accounts_google.scheme = PasswordForm::SCHEME_HTML; 230 accounts_google.signon_realm = "https://accounts.google.com"; 231 std::vector<PasswordForm*> accounts_google_expected; 232 accounts_google_expected.push_back(all_forms[3]); 233 234 // Same thing for a generic saved login. 235 PasswordForm bar_example; 236 bar_example.scheme = PasswordForm::SCHEME_HTML; 237 bar_example.signon_realm = "http://bar.example.com"; 238 std::vector<PasswordForm*> bar_example_expected; 239 bar_example_expected.push_back(all_forms[4]); 240 241 MockPasswordStoreConsumer consumer; 242 243 // Make sure we quit the MessageLoop even if the test fails. 244 ON_CALL(consumer, OnGetPasswordStoreResults(_)) 245 .WillByDefault(QuitUIMessageLoop()); 246 247 // Expect the appropriate replies, as above, in reverse order than we will 248 // issue the queries. Each retires on saturation to avoid matcher spew, except 249 // the last which quits the message loop. 250 EXPECT_CALL(consumer, 251 OnGetPasswordStoreResults(ContainsAllPasswordForms(bar_example_expected))) 252 .WillOnce(DoAll(WithArg<0>(STLDeleteElements0()), QuitUIMessageLoop())); 253 EXPECT_CALL(consumer, 254 OnGetPasswordStoreResults( 255 ContainsAllPasswordForms(accounts_google_expected))) 256 .WillOnce(WithArg<0>(STLDeleteElements0())).RetiresOnSaturation(); 257 EXPECT_CALL(consumer, 258 OnGetPasswordStoreResults( 259 ContainsAllPasswordForms(www_google_expected))) 260 .WillOnce(WithArg<0>(STLDeleteElements0())).RetiresOnSaturation(); 261 262 store->GetLogins(www_google, &consumer); 263 store->GetLogins(accounts_google, &consumer); 264 store->GetLogins(bar_example, &consumer); 265 266 base::MessageLoop::current()->Run(); 267 268 STLDeleteElements(&all_forms); 269 } 270