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