| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 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/message_loop/message_loop.h" | |
| 6 #include "base/prefs/pref_service.h" | |
| 7 #include "base/strings/stringprintf.h" | |
| 8 #include "base/synchronization/waitable_event.h" | |
| 9 #include "chrome/browser/browser_process.h" | |
| 10 #include "chrome/browser/chrome_notification_types.h" | |
| 11 #include "chrome/browser/chromeos/login/oauth2_login_manager.h" | |
| 12 #include "chrome/browser/chromeos/login/oauth2_login_manager_factory.h" | |
| 13 #include "chrome/browser/chromeos/login/oobe_base_test.h" | |
| 14 #include "chrome/browser/chromeos/login/user_manager.h" | |
| 15 #include "chrome/browser/chromeos/login/wizard_controller.h" | |
| 16 #include "chrome/browser/extensions/extension_test_message_listener.h" | |
| 17 #include "chrome/browser/profiles/profile_manager.h" | |
| 18 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" | |
| 19 #include "chrome/browser/ui/app_modal_dialogs/javascript_app_modal_dialog.h" | |
| 20 #include "chrome/browser/ui/app_modal_dialogs/native_app_modal_dialog.h" | |
| 21 #include "chrome/browser/ui/browser.h" | |
| 22 #include "chrome/browser/ui/browser_tabstrip.h" | |
| 23 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" | |
| 24 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
| 25 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h" | |
| 26 #include "chrome/test/base/ui_test_utils.h" | |
| 27 #include "components/signin/core/browser/profile_oauth2_token_service.h" | |
| 28 #include "content/public/browser/notification_service.h" | |
| 29 #include "content/public/test/browser_test_utils.h" | |
| 30 #include "extensions/browser/process_manager.h" | |
| 31 #include "google_apis/gaia/gaia_constants.h" | |
| 32 #include "google_apis/gaia/gaia_urls.h" | |
| 33 #include "net/cookies/canonical_cookie.h" | |
| 34 #include "net/cookies/cookie_monster.h" | |
| 35 #include "net/cookies/cookie_store.h" | |
| 36 #include "net/test/embedded_test_server/http_request.h" | |
| 37 #include "net/test/embedded_test_server/http_response.h" | |
| 38 #include "net/url_request/url_request_context.h" | |
| 39 #include "net/url_request/url_request_context_getter.h" | |
| 40 | |
| 41 using net::test_server::BasicHttpResponse; | |
| 42 using net::test_server::HttpRequest; | |
| 43 using net::test_server::HttpResponse; | |
| 44 | |
| 45 namespace chromeos { | |
| 46 | |
| 47 namespace { | |
| 48 | |
| 49 // Email of owner account for test. | |
| 50 const char kTestAccountId[] = "username@gmail.com"; | |
| 51 const char kTestRawAccountId[] = "User.Name"; | |
| 52 const char kTestAccountPassword[] = "fake-password"; | |
| 53 const char kTestAuthCode[] = "fake-auth-code"; | |
| 54 const char kTestGaiaUberToken[] = "fake-uber-token"; | |
| 55 const char kTestAuthLoginAccessToken[] = "fake-access-token"; | |
| 56 const char kTestRefreshToken[] = "fake-refresh-token"; | |
| 57 const char kTestAuthSIDCookie[] = "fake-auth-SID-cookie"; | |
| 58 const char kTestAuthLSIDCookie[] = "fake-auth-LSID-cookie"; | |
| 59 const char kTestSessionSIDCookie[] = "fake-session-SID-cookie"; | |
| 60 const char kTestSessionLSIDCookie[] = "fake-session-LSID-cookie"; | |
| 61 const char kTestSession2SIDCookie[] = "fake-session2-SID-cookie"; | |
| 62 const char kTestSession2LSIDCookie[] = "fake-session2-LSID-cookie"; | |
| 63 const char kTestUserinfoToken[] = "fake-userinfo-token"; | |
| 64 const char kTestLoginToken[] = "fake-login-token"; | |
| 65 const char kTestSyncToken[] = "fake-sync-token"; | |
| 66 const char kTestAuthLoginToken[] = "fake-oauthlogin-token"; | |
| 67 | |
| 68 class OAuth2LoginManagerStateWaiter : public OAuth2LoginManager::Observer { | |
| 69 public: | |
| 70 explicit OAuth2LoginManagerStateWaiter(Profile* profile) | |
| 71 : profile_(profile), | |
| 72 waiting_for_state_(false), | |
| 73 final_state_(OAuth2LoginManager::SESSION_RESTORE_NOT_STARTED) { | |
| 74 } | |
| 75 | |
| 76 void WaitForStates( | |
| 77 const std::set<OAuth2LoginManager::SessionRestoreState>& states) { | |
| 78 DCHECK(!waiting_for_state_); | |
| 79 OAuth2LoginManager* login_manager = | |
| 80 OAuth2LoginManagerFactory::GetInstance()->GetForProfile(profile_); | |
| 81 states_ = states; | |
| 82 if (states_.find(login_manager->state()) != states_.end()) { | |
| 83 final_state_ = login_manager->state(); | |
| 84 return; | |
| 85 } | |
| 86 | |
| 87 waiting_for_state_ = true; | |
| 88 login_manager->AddObserver(this); | |
| 89 runner_ = new content::MessageLoopRunner; | |
| 90 runner_->Run(); | |
| 91 login_manager->RemoveObserver(this); | |
| 92 } | |
| 93 | |
| 94 OAuth2LoginManager::SessionRestoreState final_state() { return final_state_; } | |
| 95 | |
| 96 private: | |
| 97 // OAuth2LoginManager::Observer overrides. | |
| 98 virtual void OnSessionRestoreStateChanged( | |
| 99 Profile* user_profile, | |
| 100 OAuth2LoginManager::SessionRestoreState state) OVERRIDE { | |
| 101 if (!waiting_for_state_) | |
| 102 return; | |
| 103 | |
| 104 if (states_.find(state) == states_.end()) | |
| 105 return; | |
| 106 | |
| 107 final_state_ = state; | |
| 108 waiting_for_state_ = false; | |
| 109 runner_->Quit(); | |
| 110 } | |
| 111 | |
| 112 Profile* profile_; | |
| 113 std::set<OAuth2LoginManager::SessionRestoreState> states_; | |
| 114 bool waiting_for_state_; | |
| 115 OAuth2LoginManager::SessionRestoreState final_state_; | |
| 116 scoped_refptr<content::MessageLoopRunner> runner_; | |
| 117 | |
| 118 DISALLOW_COPY_AND_ASSIGN(OAuth2LoginManagerStateWaiter); | |
| 119 }; | |
| 120 | |
| 121 } // namespace | |
| 122 | |
| 123 class OAuth2Test : public OobeBaseTest { | |
| 124 protected: | |
| 125 OAuth2Test() {} | |
| 126 | |
| 127 void SetupGaiaServerForNewAccount() { | |
| 128 FakeGaia::MergeSessionParams params; | |
| 129 params.auth_sid_cookie = kTestAuthSIDCookie; | |
| 130 params.auth_lsid_cookie = kTestAuthLSIDCookie; | |
| 131 params.auth_code = kTestAuthCode; | |
| 132 params.refresh_token = kTestRefreshToken; | |
| 133 params.access_token = kTestAuthLoginAccessToken; | |
| 134 params.gaia_uber_token = kTestGaiaUberToken; | |
| 135 params.session_sid_cookie = kTestSessionSIDCookie; | |
| 136 params.session_lsid_cookie = kTestSessionLSIDCookie; | |
| 137 fake_gaia_->SetMergeSessionParams(params); | |
| 138 SetupGaiaServerWithAccessTokens(); | |
| 139 } | |
| 140 | |
| 141 void SetupGaiaServerForUnexpiredAccount() { | |
| 142 FakeGaia::MergeSessionParams params; | |
| 143 params.email = kTestAccountId; | |
| 144 fake_gaia_->SetMergeSessionParams(params); | |
| 145 SetupGaiaServerWithAccessTokens(); | |
| 146 } | |
| 147 | |
| 148 void SetupGaiaServerForExpiredAccount() { | |
| 149 FakeGaia::MergeSessionParams params; | |
| 150 params.gaia_uber_token = kTestGaiaUberToken; | |
| 151 params.session_sid_cookie = kTestSession2SIDCookie; | |
| 152 params.session_lsid_cookie = kTestSession2LSIDCookie; | |
| 153 fake_gaia_->SetMergeSessionParams(params); | |
| 154 SetupGaiaServerWithAccessTokens(); | |
| 155 } | |
| 156 | |
| 157 void LoginAsExistingUser() { | |
| 158 content::WindowedNotificationObserver( | |
| 159 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, | |
| 160 content::NotificationService::AllSources()).Wait(); | |
| 161 | |
| 162 JsExpect("!!document.querySelector('#account-picker')"); | |
| 163 JsExpect("!!document.querySelector('#pod-row')"); | |
| 164 | |
| 165 EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId), | |
| 166 User::OAUTH2_TOKEN_STATUS_VALID); | |
| 167 | |
| 168 EXPECT_TRUE(TryToLogin(kTestAccountId, kTestAccountPassword)); | |
| 169 Profile* profile = ProfileManager::GetPrimaryUserProfile(); | |
| 170 | |
| 171 // Wait for the session merge to finish. | |
| 172 WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_DONE); | |
| 173 | |
| 174 // Check for existance of refresh token. | |
| 175 ProfileOAuth2TokenService* token_service = | |
| 176 ProfileOAuth2TokenServiceFactory::GetForProfile(profile); | |
| 177 EXPECT_TRUE(token_service->RefreshTokenIsAvailable(kTestAccountId)); | |
| 178 | |
| 179 EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId), | |
| 180 User::OAUTH2_TOKEN_STATUS_VALID); | |
| 181 } | |
| 182 | |
| 183 bool TryToLogin(const std::string& username, | |
| 184 const std::string& password) { | |
| 185 if (!AddUserToSession(username, password)) | |
| 186 return false; | |
| 187 | |
| 188 if (const User* active_user = UserManager::Get()->GetActiveUser()) | |
| 189 return active_user->email() == username; | |
| 190 | |
| 191 return false; | |
| 192 } | |
| 193 | |
| 194 User::OAuthTokenStatus GetOAuthStatusFromLocalState( | |
| 195 const std::string& user_id) const { | |
| 196 PrefService* local_state = g_browser_process->local_state(); | |
| 197 const base::DictionaryValue* prefs_oauth_status = | |
| 198 local_state->GetDictionary("OAuthTokenStatus"); | |
| 199 int oauth_token_status = User::OAUTH_TOKEN_STATUS_UNKNOWN; | |
| 200 if (prefs_oauth_status && | |
| 201 prefs_oauth_status->GetIntegerWithoutPathExpansion( | |
| 202 user_id, &oauth_token_status)) { | |
| 203 User::OAuthTokenStatus result = | |
| 204 static_cast<User::OAuthTokenStatus>(oauth_token_status); | |
| 205 return result; | |
| 206 } | |
| 207 return User::OAUTH_TOKEN_STATUS_UNKNOWN; | |
| 208 } | |
| 209 | |
| 210 protected: | |
| 211 // OobeBaseTest overrides. | |
| 212 virtual Profile* profile() OVERRIDE { | |
| 213 if (UserManager::Get()->GetActiveUser()) | |
| 214 return ProfileManager::GetPrimaryUserProfile(); | |
| 215 | |
| 216 return OobeBaseTest::profile(); | |
| 217 } | |
| 218 | |
| 219 bool AddUserToSession(const std::string& username, | |
| 220 const std::string& password) { | |
| 221 ExistingUserController* controller = | |
| 222 ExistingUserController::current_controller(); | |
| 223 if (!controller) { | |
| 224 ADD_FAILURE(); | |
| 225 return false; | |
| 226 } | |
| 227 | |
| 228 controller->Login(UserContext(username, password, std::string())); | |
| 229 content::WindowedNotificationObserver( | |
| 230 chrome::NOTIFICATION_SESSION_STARTED, | |
| 231 content::NotificationService::AllSources()).Wait(); | |
| 232 const UserList& logged_users = UserManager::Get()->GetLoggedInUsers(); | |
| 233 for (UserList::const_iterator it = logged_users.begin(); | |
| 234 it != logged_users.end(); ++it) { | |
| 235 if ((*it)->email() == username) | |
| 236 return true; | |
| 237 } | |
| 238 return false; | |
| 239 } | |
| 240 | |
| 241 void SetupGaiaServerWithAccessTokens() { | |
| 242 // Configure OAuth authentication. | |
| 243 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); | |
| 244 | |
| 245 // This token satisfies the userinfo.email request from | |
| 246 // DeviceOAuth2TokenService used in token validation. | |
| 247 FakeGaia::AccessTokenInfo userinfo_token_info; | |
| 248 userinfo_token_info.token = kTestUserinfoToken; | |
| 249 userinfo_token_info.scopes.insert( | |
| 250 "https://www.googleapis.com/auth/userinfo.email"); | |
| 251 userinfo_token_info.audience = gaia_urls->oauth2_chrome_client_id(); | |
| 252 userinfo_token_info.email = kTestAccountId; | |
| 253 fake_gaia_->IssueOAuthToken(kTestRefreshToken, userinfo_token_info); | |
| 254 | |
| 255 FakeGaia::AccessTokenInfo userinfo_profile_token_info; | |
| 256 userinfo_profile_token_info.token = kTestUserinfoToken; | |
| 257 userinfo_profile_token_info.scopes.insert( | |
| 258 "https://www.googleapis.com/auth/userinfo.profile"); | |
| 259 userinfo_profile_token_info.audience = gaia_urls->oauth2_chrome_client_id(); | |
| 260 userinfo_profile_token_info.email = kTestAccountId; | |
| 261 fake_gaia_->IssueOAuthToken(kTestRefreshToken, userinfo_profile_token_info); | |
| 262 | |
| 263 // The any-api access token for accessing the token minting endpoint. | |
| 264 FakeGaia::AccessTokenInfo login_token_info; | |
| 265 login_token_info.token = kTestLoginToken; | |
| 266 login_token_info.scopes.insert(GaiaConstants::kAnyApiOAuth2Scope); | |
| 267 login_token_info.audience = gaia_urls->oauth2_chrome_client_id(); | |
| 268 fake_gaia_->IssueOAuthToken(kTestRefreshToken, login_token_info); | |
| 269 | |
| 270 // The /auth/chromesync access token for accessing sync endpoint. | |
| 271 FakeGaia::AccessTokenInfo sync_token_info; | |
| 272 sync_token_info.token = kTestSyncToken; | |
| 273 sync_token_info.scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); | |
| 274 sync_token_info.audience = gaia_urls->oauth2_chrome_client_id(); | |
| 275 fake_gaia_->IssueOAuthToken(kTestRefreshToken, sync_token_info); | |
| 276 | |
| 277 FakeGaia::AccessTokenInfo auth_login_token_info; | |
| 278 auth_login_token_info.token = kTestAuthLoginToken; | |
| 279 auth_login_token_info.scopes.insert(GaiaConstants::kOAuth1LoginScope); | |
| 280 auth_login_token_info.audience = gaia_urls->oauth2_chrome_client_id(); | |
| 281 fake_gaia_->IssueOAuthToken(kTestRefreshToken, auth_login_token_info); | |
| 282 } | |
| 283 | |
| 284 void CheckSessionState(OAuth2LoginManager::SessionRestoreState state) { | |
| 285 OAuth2LoginManager* login_manager = | |
| 286 OAuth2LoginManagerFactory::GetInstance()->GetForProfile( | |
| 287 profile()); | |
| 288 ASSERT_EQ(state, login_manager->state()); | |
| 289 } | |
| 290 | |
| 291 void WaitForMergeSessionCompletion( | |
| 292 OAuth2LoginManager::SessionRestoreState final_state) { | |
| 293 // Wait for the session merge to finish. | |
| 294 std::set<OAuth2LoginManager::SessionRestoreState> states; | |
| 295 states.insert(OAuth2LoginManager::SESSION_RESTORE_DONE); | |
| 296 states.insert(OAuth2LoginManager::SESSION_RESTORE_FAILED); | |
| 297 states.insert(OAuth2LoginManager::SESSION_RESTORE_CONNECTION_FAILED); | |
| 298 OAuth2LoginManagerStateWaiter merge_session_waiter(profile()); | |
| 299 merge_session_waiter.WaitForStates(states); | |
| 300 EXPECT_EQ(merge_session_waiter.final_state(), final_state); | |
| 301 } | |
| 302 | |
| 303 void StartNewUserSession(bool wait_for_merge) { | |
| 304 SetupGaiaServerForNewAccount(); | |
| 305 SimulateNetworkOnline(); | |
| 306 chromeos::WizardController::SkipPostLoginScreensForTesting(); | |
| 307 chromeos::WizardController* wizard_controller = | |
| 308 chromeos::WizardController::default_controller(); | |
| 309 wizard_controller->SkipToLoginForTesting(LoginScreenContext()); | |
| 310 | |
| 311 content::WindowedNotificationObserver( | |
| 312 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, | |
| 313 content::NotificationService::AllSources()).Wait(); | |
| 314 | |
| 315 // Use capitalized and dotted user name on purpose to make sure | |
| 316 // our email normalization kicks in. | |
| 317 GetLoginDisplay()->ShowSigninScreenForCreds(kTestRawAccountId, | |
| 318 kTestAccountPassword); | |
| 319 | |
| 320 content::WindowedNotificationObserver( | |
| 321 chrome::NOTIFICATION_SESSION_STARTED, | |
| 322 content::NotificationService::AllSources()).Wait(); | |
| 323 | |
| 324 if (wait_for_merge) { | |
| 325 // Wait for the session merge to finish. | |
| 326 WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_DONE); | |
| 327 } | |
| 328 } | |
| 329 | |
| 330 DISALLOW_COPY_AND_ASSIGN(OAuth2Test); | |
| 331 }; | |
| 332 | |
| 333 class CookieReader : public base::RefCountedThreadSafe<CookieReader> { | |
| 334 public: | |
| 335 CookieReader() { | |
| 336 } | |
| 337 | |
| 338 void ReadCookies(Profile* profile) { | |
| 339 context_ = profile->GetRequestContext(); | |
| 340 content::BrowserThread::PostTask( | |
| 341 content::BrowserThread::IO, FROM_HERE, | |
| 342 base::Bind(&CookieReader::ReadCookiesOnIOThread, | |
| 343 this)); | |
| 344 runner_ = new content::MessageLoopRunner; | |
| 345 runner_->Run(); | |
| 346 } | |
| 347 | |
| 348 std::string GetCookieValue(const std::string& name) { | |
| 349 for (std::vector<net::CanonicalCookie>::const_iterator iter = | |
| 350 cookie_list_.begin(); | |
| 351 iter != cookie_list_.end(); | |
| 352 ++iter) { | |
| 353 if (iter->Name() == name) { | |
| 354 return iter->Value(); | |
| 355 } | |
| 356 } | |
| 357 return std::string(); | |
| 358 } | |
| 359 | |
| 360 private: | |
| 361 friend class base::RefCountedThreadSafe<CookieReader>; | |
| 362 | |
| 363 virtual ~CookieReader() { | |
| 364 } | |
| 365 | |
| 366 void ReadCookiesOnIOThread() { | |
| 367 context_->GetURLRequestContext()->cookie_store()->GetCookieMonster()-> | |
| 368 GetAllCookiesAsync(base::Bind( | |
| 369 &CookieReader::OnGetAllCookiesOnUIThread, | |
| 370 this)); | |
| 371 } | |
| 372 | |
| 373 void OnGetAllCookiesOnUIThread(const net::CookieList& cookies) { | |
| 374 cookie_list_ = cookies; | |
| 375 content::BrowserThread::PostTask( | |
| 376 content::BrowserThread::UI, FROM_HERE, | |
| 377 base::Bind(&CookieReader::OnCookiesReadyOnUIThread, | |
| 378 this)); | |
| 379 } | |
| 380 | |
| 381 void OnCookiesReadyOnUIThread() { | |
| 382 runner_->Quit(); | |
| 383 } | |
| 384 | |
| 385 scoped_refptr<net::URLRequestContextGetter> context_; | |
| 386 net::CookieList cookie_list_; | |
| 387 scoped_refptr<content::MessageLoopRunner> runner_; | |
| 388 | |
| 389 DISALLOW_COPY_AND_ASSIGN(CookieReader); | |
| 390 }; | |
| 391 | |
| 392 // PRE_MergeSession is testing merge session for a new profile. | |
| 393 IN_PROC_BROWSER_TEST_F(OAuth2Test, PRE_PRE_PRE_MergeSession) { | |
| 394 StartNewUserSession(true); | |
| 395 // Check for existance of refresh token. | |
| 396 ProfileOAuth2TokenService* token_service = | |
| 397 ProfileOAuth2TokenServiceFactory::GetForProfile( | |
| 398 profile()); | |
| 399 EXPECT_TRUE(token_service->RefreshTokenIsAvailable(kTestAccountId)); | |
| 400 | |
| 401 EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId), | |
| 402 User::OAUTH2_TOKEN_STATUS_VALID); | |
| 403 | |
| 404 scoped_refptr<CookieReader> cookie_reader(new CookieReader()); | |
| 405 cookie_reader->ReadCookies(profile()); | |
| 406 EXPECT_EQ(cookie_reader->GetCookieValue("SID"), kTestSessionSIDCookie); | |
| 407 EXPECT_EQ(cookie_reader->GetCookieValue("LSID"), kTestSessionLSIDCookie); | |
| 408 } | |
| 409 | |
| 410 // MergeSession test is running merge session process for an existing profile | |
| 411 // that was generated in PRE_PRE_PRE_MergeSession test. In this test, we | |
| 412 // are not running /MergeSession process since the /ListAccounts call confirms | |
| 413 // that the session is not stale. | |
| 414 IN_PROC_BROWSER_TEST_F(OAuth2Test, PRE_PRE_MergeSession) { | |
| 415 SetupGaiaServerForUnexpiredAccount(); | |
| 416 SimulateNetworkOnline(); | |
| 417 LoginAsExistingUser(); | |
| 418 scoped_refptr<CookieReader> cookie_reader(new CookieReader()); | |
| 419 cookie_reader->ReadCookies(profile()); | |
| 420 // These are still cookie values form the initial session since | |
| 421 // /ListAccounts | |
| 422 EXPECT_EQ(cookie_reader->GetCookieValue("SID"), kTestSessionSIDCookie); | |
| 423 EXPECT_EQ(cookie_reader->GetCookieValue("LSID"), kTestSessionLSIDCookie); | |
| 424 } | |
| 425 | |
| 426 // MergeSession test is running merge session process for an existing profile | |
| 427 // that was generated in PRE_PRE_MergeSession test. | |
| 428 IN_PROC_BROWSER_TEST_F(OAuth2Test, PRE_MergeSession) { | |
| 429 SetupGaiaServerForExpiredAccount(); | |
| 430 SimulateNetworkOnline(); | |
| 431 LoginAsExistingUser(); | |
| 432 scoped_refptr<CookieReader> cookie_reader(new CookieReader()); | |
| 433 cookie_reader->ReadCookies(profile()); | |
| 434 // These should be cookie values that we generated by calling /MergeSession, | |
| 435 // since /ListAccounts should have tell us that the initial session cookies | |
| 436 // are stale. | |
| 437 EXPECT_EQ(cookie_reader->GetCookieValue("SID"), kTestSession2SIDCookie); | |
| 438 EXPECT_EQ(cookie_reader->GetCookieValue("LSID"), kTestSession2LSIDCookie); | |
| 439 } | |
| 440 | |
| 441 // MergeSession test is attempting to merge session for an existing profile | |
| 442 // that was generated in PRE_PRE_MergeSession test. This attempt should fail | |
| 443 // since FakeGaia instance isn't configured to return relevant tokens/cookies. | |
| 444 IN_PROC_BROWSER_TEST_F(OAuth2Test, MergeSession) { | |
| 445 SimulateNetworkOnline(); | |
| 446 | |
| 447 content::WindowedNotificationObserver( | |
| 448 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, | |
| 449 content::NotificationService::AllSources()).Wait(); | |
| 450 | |
| 451 JsExpect("!!document.querySelector('#account-picker')"); | |
| 452 JsExpect("!!document.querySelector('#pod-row')"); | |
| 453 | |
| 454 EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId), | |
| 455 User::OAUTH2_TOKEN_STATUS_VALID); | |
| 456 | |
| 457 EXPECT_TRUE(TryToLogin(kTestAccountId, kTestAccountPassword)); | |
| 458 | |
| 459 // Wait for the session merge to finish. | |
| 460 WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_FAILED); | |
| 461 | |
| 462 EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId), | |
| 463 User::OAUTH2_TOKEN_STATUS_INVALID); | |
| 464 } | |
| 465 | |
| 466 | |
| 467 const char kGooglePageContent[] = | |
| 468 "<html><title>Hello!</title><script>alert('hello');</script>" | |
| 469 "<body>Hello Google!</body></html>"; | |
| 470 const char kRandomPageContent[] = | |
| 471 "<html><title>SomthingElse</title><body>I am SomethingElse</body></html>"; | |
| 472 const char kHelloPagePath[] = "/hello_google"; | |
| 473 const char kRandomPagePath[] = "/non_google_page"; | |
| 474 | |
| 475 | |
| 476 // FakeGoogle serves content of http://www.google.com/hello_google page for | |
| 477 // merge session tests. | |
| 478 class FakeGoogle { | |
| 479 public: | |
| 480 FakeGoogle() : start_event_(true, false) { | |
| 481 } | |
| 482 | |
| 483 ~FakeGoogle() {} | |
| 484 | |
| 485 scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) { | |
| 486 // The scheme and host of the URL is actually not important but required to | |
| 487 // get a valid GURL in order to parse |request.relative_url|. | |
| 488 GURL request_url = GURL("http://localhost").Resolve(request.relative_url); | |
| 489 LOG(WARNING) << "Requesting page " << request.relative_url; | |
| 490 std::string request_path = request_url.path(); | |
| 491 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); | |
| 492 if (request_path == kHelloPagePath) { // Serving "google" page. | |
| 493 start_event_.Signal(); | |
| 494 content::BrowserThread::PostTask( | |
| 495 content::BrowserThread::UI, FROM_HERE, | |
| 496 base::Bind(&FakeGoogle::QuitRunnerOnUIThread, | |
| 497 base::Unretained(this))); | |
| 498 | |
| 499 http_response->set_code(net::HTTP_OK); | |
| 500 http_response->set_content_type("text/html"); | |
| 501 http_response->set_content(kGooglePageContent); | |
| 502 } else if (request_path == kRandomPagePath) { // Serving "non-google" page. | |
| 503 http_response->set_code(net::HTTP_OK); | |
| 504 http_response->set_content_type("text/html"); | |
| 505 http_response->set_content(kRandomPageContent); | |
| 506 } else { | |
| 507 return scoped_ptr<HttpResponse>(); // Request not understood. | |
| 508 } | |
| 509 | |
| 510 return http_response.PassAs<HttpResponse>(); | |
| 511 } | |
| 512 | |
| 513 // True if we have already served the test page. | |
| 514 bool IsPageRequested () { | |
| 515 return start_event_.IsSignaled(); | |
| 516 } | |
| 517 | |
| 518 // Waits until we receive a request to serve the test page. | |
| 519 void WaitForPageRequest() { | |
| 520 // If we have already served the request, bail out. | |
| 521 if (start_event_.IsSignaled()) | |
| 522 return; | |
| 523 | |
| 524 runner_ = new content::MessageLoopRunner; | |
| 525 runner_->Run(); | |
| 526 } | |
| 527 | |
| 528 private: | |
| 529 void QuitRunnerOnUIThread() { | |
| 530 if (runner_.get()) | |
| 531 runner_->Quit(); | |
| 532 } | |
| 533 // This event will tell us when we actually see HTTP request on the server | |
| 534 // side. It should be signalled only after the page/XHR throttle had been | |
| 535 // removed (after merge session completes). | |
| 536 base::WaitableEvent start_event_; | |
| 537 scoped_refptr<content::MessageLoopRunner> runner_; | |
| 538 | |
| 539 DISALLOW_COPY_AND_ASSIGN(FakeGoogle); | |
| 540 }; | |
| 541 | |
| 542 // FakeGaia specialization that can delay /MergeSession handler until | |
| 543 // we explicitly call DelayedFakeGaia::UnblockMergeSession(). | |
| 544 class DelayedFakeGaia : public FakeGaia { | |
| 545 public: | |
| 546 DelayedFakeGaia() | |
| 547 : blocking_event_(true, false), | |
| 548 start_event_(true, false) { | |
| 549 } | |
| 550 | |
| 551 void UnblockMergeSession() { | |
| 552 blocking_event_.Signal(); | |
| 553 } | |
| 554 | |
| 555 void WaitForMergeSessionToStart() { | |
| 556 // If we have already served the request, bail out. | |
| 557 if (start_event_.IsSignaled()) | |
| 558 return; | |
| 559 | |
| 560 runner_ = new content::MessageLoopRunner; | |
| 561 runner_->Run(); | |
| 562 } | |
| 563 | |
| 564 private: | |
| 565 // FakeGaia overrides. | |
| 566 virtual void HandleMergeSession(const HttpRequest& request, | |
| 567 BasicHttpResponse* http_response) OVERRIDE { | |
| 568 start_event_.Signal(); | |
| 569 content::BrowserThread::PostTask( | |
| 570 content::BrowserThread::UI, FROM_HERE, | |
| 571 base::Bind(&DelayedFakeGaia::QuitRunnerOnUIThread, | |
| 572 base::Unretained(this))); | |
| 573 blocking_event_.Wait(); | |
| 574 FakeGaia::HandleMergeSession(request, http_response); | |
| 575 } | |
| 576 | |
| 577 void QuitRunnerOnUIThread() { | |
| 578 if (runner_.get()) | |
| 579 runner_->Quit(); | |
| 580 } | |
| 581 | |
| 582 base::WaitableEvent blocking_event_; | |
| 583 base::WaitableEvent start_event_; | |
| 584 scoped_refptr<content::MessageLoopRunner> runner_; | |
| 585 | |
| 586 DISALLOW_COPY_AND_ASSIGN(DelayedFakeGaia); | |
| 587 }; | |
| 588 | |
| 589 class MergeSessionTest : public OAuth2Test { | |
| 590 protected: | |
| 591 MergeSessionTest() : delayed_fake_gaia_(new DelayedFakeGaia()) { | |
| 592 fake_gaia_.reset(delayed_fake_gaia_); | |
| 593 } | |
| 594 | |
| 595 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { | |
| 596 OAuth2Test::SetUpCommandLine(command_line); | |
| 597 | |
| 598 // Get fake URL for fake google.com. | |
| 599 const GURL& server_url = embedded_test_server()->base_url(); | |
| 600 std::string google_host("www.google.com"); | |
| 601 GURL::Replacements replace_google_host; | |
| 602 replace_google_host.SetHostStr(google_host); | |
| 603 GURL google_url = server_url.ReplaceComponents(replace_google_host); | |
| 604 fake_google_page_url_ = google_url.Resolve(kHelloPagePath); | |
| 605 | |
| 606 std::string non_google_host("www.somethingelse.org"); | |
| 607 GURL::Replacements replace_non_google_host; | |
| 608 replace_non_google_host.SetHostStr(non_google_host); | |
| 609 GURL non_google_url = server_url.ReplaceComponents(replace_non_google_host); | |
| 610 non_google_page_url_ = non_google_url.Resolve(kRandomPagePath); | |
| 611 } | |
| 612 | |
| 613 virtual void SetUp() OVERRIDE { | |
| 614 embedded_test_server()->RegisterRequestHandler( | |
| 615 base::Bind(&FakeGoogle::HandleRequest, | |
| 616 base::Unretained(&fake_google_))); | |
| 617 OAuth2Test::SetUp(); | |
| 618 } | |
| 619 | |
| 620 protected: | |
| 621 void UnblockMergeSession() { | |
| 622 delayed_fake_gaia_->UnblockMergeSession(); | |
| 623 } | |
| 624 | |
| 625 void WaitForMergeSessionToStart() { | |
| 626 delayed_fake_gaia_->WaitForMergeSessionToStart(); | |
| 627 } | |
| 628 | |
| 629 void JsExpect(content::WebContents* contents, | |
| 630 const std::string& expression) { | |
| 631 bool result; | |
| 632 ASSERT_TRUE(content::ExecuteScriptAndExtractBool( | |
| 633 contents, | |
| 634 "window.domAutomationController.send(!!(" + expression + "));", | |
| 635 &result)); | |
| 636 ASSERT_TRUE(result) << expression; | |
| 637 } | |
| 638 | |
| 639 const GURL& GetBackGroundPageUrl(const std::string& extension_id) { | |
| 640 extensions::ProcessManager* manager = | |
| 641 extensions::ExtensionSystem::Get(profile())->process_manager(); | |
| 642 extensions::ExtensionHost* host = | |
| 643 manager->GetBackgroundHostForExtension(extension_id); | |
| 644 return host->host_contents()->GetURL(); | |
| 645 } | |
| 646 | |
| 647 void JsExpectOnBackgroundPage(const std::string& extension_id, | |
| 648 const std::string& expression) { | |
| 649 extensions::ProcessManager* manager = | |
| 650 extensions::ExtensionSystem::Get(profile())->process_manager(); | |
| 651 extensions::ExtensionHost* host = | |
| 652 manager->GetBackgroundHostForExtension(extension_id); | |
| 653 if (host == NULL) { | |
| 654 ADD_FAILURE() << "Extension " << extension_id | |
| 655 << " has no background page."; | |
| 656 return; | |
| 657 } | |
| 658 | |
| 659 JsExpect(host->host_contents(), expression); | |
| 660 } | |
| 661 | |
| 662 FakeGoogle fake_google_; | |
| 663 DelayedFakeGaia* delayed_fake_gaia_; | |
| 664 GURL fake_google_page_url_; | |
| 665 GURL non_google_page_url_; | |
| 666 | |
| 667 private: | |
| 668 DISALLOW_COPY_AND_ASSIGN(MergeSessionTest); | |
| 669 }; | |
| 670 | |
| 671 Browser* FindOrCreateVisibleBrowser(Profile* profile) { | |
| 672 chrome::ScopedTabbedBrowserDisplayer displayer( | |
| 673 profile, chrome::GetActiveDesktop()); | |
| 674 Browser* browser = displayer.browser(); | |
| 675 if (browser->tab_strip_model()->count() == 0) | |
| 676 chrome::AddTabAt(browser, GURL(), -1, true); | |
| 677 return browser; | |
| 678 } | |
| 679 | |
| 680 IN_PROC_BROWSER_TEST_F(MergeSessionTest, PageThrottle) { | |
| 681 StartNewUserSession(false); | |
| 682 | |
| 683 // Try to open a page from google.com. | |
| 684 Browser* browser = | |
| 685 FindOrCreateVisibleBrowser(profile()); | |
| 686 ui_test_utils::NavigateToURLWithDisposition( | |
| 687 browser, | |
| 688 fake_google_page_url_, | |
| 689 CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE); | |
| 690 | |
| 691 // Wait until we get send merge session request. | |
| 692 WaitForMergeSessionToStart(); | |
| 693 | |
| 694 // Make sure the page is blocked by the throttle. | |
| 695 EXPECT_FALSE(fake_google_.IsPageRequested()); | |
| 696 | |
| 697 // Check that throttle page is displayed instead. | |
| 698 base::string16 title; | |
| 699 ui_test_utils::GetCurrentTabTitle(browser, &title); | |
| 700 DVLOG(1) << "Loaded page at the start : " << title; | |
| 701 | |
| 702 // Unblock GAIA request. | |
| 703 UnblockMergeSession(); | |
| 704 | |
| 705 // Wait for the session merge to finish. | |
| 706 WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_DONE); | |
| 707 | |
| 708 // Make sure the test page is served. | |
| 709 fake_google_.WaitForPageRequest(); | |
| 710 | |
| 711 // Check that real page is no longer blocked by the throttle and that the | |
| 712 // real page pops up JS dialog. | |
| 713 AppModalDialog* dialog = ui_test_utils::WaitForAppModalDialog(); | |
| 714 ASSERT_TRUE(dialog->IsJavaScriptModalDialog()); | |
| 715 JavaScriptAppModalDialog* js_dialog = | |
| 716 static_cast<JavaScriptAppModalDialog*>(dialog); | |
| 717 js_dialog->native_dialog()->AcceptAppModalDialog(); | |
| 718 | |
| 719 ui_test_utils::GetCurrentTabTitle(browser, &title); | |
| 720 DVLOG(1) << "Loaded page at the end : " << title; | |
| 721 } | |
| 722 | |
| 723 IN_PROC_BROWSER_TEST_F(MergeSessionTest, XHRThrottle) { | |
| 724 StartNewUserSession(false); | |
| 725 | |
| 726 // Wait until we get send merge session request. | |
| 727 WaitForMergeSessionToStart(); | |
| 728 | |
| 729 // Reset ExtensionBrowserTest::observer_ to the right browser object. | |
| 730 Browser* browser = FindOrCreateVisibleBrowser(profile()); | |
| 731 observer_.reset(new ExtensionTestNotificationObserver(browser)); | |
| 732 | |
| 733 // Run background page tests. The tests will just wait for XHR request | |
| 734 // to complete. | |
| 735 ResultCatcher catcher; | |
| 736 | |
| 737 scoped_ptr<ExtensionTestMessageListener> non_google_xhr_listener( | |
| 738 new ExtensionTestMessageListener("non-google-xhr-received", false)); | |
| 739 | |
| 740 // Load extension with a background page. The background page will | |
| 741 // attempt to load |fake_google_page_url_| via XHR. | |
| 742 const extensions::Extension* ext = LoadExtension( | |
| 743 test_data_dir_.AppendASCII("merge_session")); | |
| 744 ASSERT_TRUE(ext); | |
| 745 | |
| 746 // Kick off XHR request from the extension. | |
| 747 JsExpectOnBackgroundPage( | |
| 748 ext->id(), | |
| 749 base::StringPrintf("startThrottledTests('%s', '%s')", | |
| 750 fake_google_page_url_.spec().c_str(), | |
| 751 non_google_page_url_.spec().c_str())); | |
| 752 | |
| 753 // Verify that we've sent XHR request form the extension side... | |
| 754 JsExpectOnBackgroundPage(ext->id(), | |
| 755 "googleRequestSent && !googleResponseReceived"); | |
| 756 | |
| 757 // ...but didn't see it on the server side yet. | |
| 758 EXPECT_FALSE(fake_google_.IsPageRequested()); | |
| 759 | |
| 760 // Unblock GAIA request. | |
| 761 UnblockMergeSession(); | |
| 762 | |
| 763 // Wait for the session merge to finish. | |
| 764 WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_DONE); | |
| 765 | |
| 766 // Wait until non-google XHR content to load first. | |
| 767 ASSERT_TRUE(non_google_xhr_listener->WaitUntilSatisfied()); | |
| 768 | |
| 769 if (!catcher.GetNextResult()) { | |
| 770 std::string message = catcher.message(); | |
| 771 ADD_FAILURE() << "Tests failed: " << message; | |
| 772 } | |
| 773 | |
| 774 EXPECT_TRUE(fake_google_.IsPageRequested()); | |
| 775 } | |
| 776 | |
| 777 } // namespace chromeos | |
| OLD | NEW |