Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / sync / one_click_signin_helper.cc
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 "chrome/browser/ui/sync/one_click_signin_helper.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <utility>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/callback_forward.h"
14 #include "base/callback_helpers.h"
15 #include "base/compiler_specific.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop/message_loop_proxy.h"
18 #include "base/metrics/field_trial.h"
19 #include "base/metrics/histogram.h"
20 #include "base/prefs/pref_service.h"
21 #include "base/prefs/scoped_user_pref_update.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/supports_user_data.h"
26 #include "base/values.h"
27 #include "chrome/browser/browser_process.h"
28 #include "chrome/browser/chrome_notification_types.h"
29 #include "chrome/browser/defaults.h"
30 #include "chrome/browser/google/google_util.h"
31 #include "chrome/browser/history/history_service.h"
32 #include "chrome/browser/history/history_service_factory.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "chrome/browser/profiles/profile_info_cache.h"
35 #include "chrome/browser/profiles/profile_io_data.h"
36 #include "chrome/browser/profiles/profile_manager.h"
37 #include "chrome/browser/search/search.h"
38 #include "chrome/browser/signin/chrome_signin_client.h"
39 #include "chrome/browser/signin/chrome_signin_client_factory.h"
40 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
41 #include "chrome/browser/signin/signin_manager.h"
42 #include "chrome/browser/signin/signin_manager_factory.h"
43 #include "chrome/browser/signin/signin_names_io_thread.h"
44 #include "chrome/browser/sync/profile_sync_service.h"
45 #include "chrome/browser/sync/profile_sync_service_factory.h"
46 #include "chrome/browser/tab_contents/tab_util.h"
47 #include "chrome/browser/ui/browser_finder.h"
48 #include "chrome/browser/ui/browser_list.h"
49 #include "chrome/browser/ui/browser_tabstrip.h"
50 #include "chrome/browser/ui/browser_window.h"
51 #include "chrome/browser/ui/chrome_pages.h"
52 #include "chrome/browser/ui/sync/one_click_signin_histogram.h"
53 #include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
54 #include "chrome/browser/ui/sync/signin_histogram.h"
55 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
56 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
57 #include "chrome/browser/ui/tabs/tab_strip_model.h"
58 #include "chrome/common/chrome_version_info.h"
59 #include "chrome/common/net/url_util.h"
60 #include "chrome/common/pref_names.h"
61 #include "chrome/common/profile_management_switches.h"
62 #include "chrome/common/url_constants.h"
63 #include "components/autofill/core/common/password_form.h"
64 #include "components/password_manager/core/browser/password_manager.h"
65 #include "components/signin/core/browser/profile_oauth2_token_service.h"
66 #include "components/signin/core/browser/signin_client.h"
67 #include "components/signin/core/browser/signin_error_controller.h"
68 #include "components/signin/core/browser/signin_manager_cookie_helper.h"
69 #include "components/sync_driver/sync_prefs.h"
70 #include "content/public/browser/browser_thread.h"
71 #include "content/public/browser/navigation_entry.h"
72 #include "content/public/browser/page_navigator.h"
73 #include "content/public/browser/render_process_host.h"
74 #include "content/public/browser/web_contents.h"
75 #include "content/public/browser/web_contents_delegate.h"
76 #include "content/public/browser/web_contents_view.h"
77 #include "content/public/common/frame_navigate_params.h"
78 #include "content/public/common/page_transition_types.h"
79 #include "google_apis/gaia/gaia_auth_util.h"
80 #include "google_apis/gaia/gaia_urls.h"
81 #include "grit/chromium_strings.h"
82 #include "grit/generated_resources.h"
83 #include "grit/theme_resources.h"
84 #include "ipc/ipc_message_macros.h"
85 #include "net/base/url_util.h"
86 #include "net/cookies/cookie_monster.h"
87 #include "net/url_request/url_request.h"
88 #include "ui/base/l10n/l10n_util.h"
89 #include "ui/base/resource/resource_bundle.h"
90 #include "url/gurl.h"
91
92
93 namespace {
94
95 // ConfirmEmailDialogDelegate -------------------------------------------------
96
97 class ConfirmEmailDialogDelegate : public TabModalConfirmDialogDelegate {
98  public:
99   enum Action {
100     CREATE_NEW_USER,
101     START_SYNC,
102     CLOSE
103   };
104
105   // Callback indicating action performed by the user.
106   typedef base::Callback<void(Action)> Callback;
107
108   // Ask the user for confirmation before starting to sync.
109   static void AskForConfirmation(content::WebContents* contents,
110                                  const std::string& last_email,
111                                  const std::string& email,
112                                  Callback callback);
113
114  private:
115   ConfirmEmailDialogDelegate(content::WebContents* contents,
116                              const std::string& last_email,
117                              const std::string& email,
118                              Callback callback);
119   virtual ~ConfirmEmailDialogDelegate();
120
121   // TabModalConfirmDialogDelegate:
122   virtual base::string16 GetTitle() OVERRIDE;
123   virtual base::string16 GetDialogMessage() OVERRIDE;
124   virtual base::string16 GetAcceptButtonTitle() OVERRIDE;
125   virtual base::string16 GetCancelButtonTitle() OVERRIDE;
126   virtual base::string16 GetLinkText() const OVERRIDE;
127   virtual void OnAccepted() OVERRIDE;
128   virtual void OnCanceled() OVERRIDE;
129   virtual void OnClosed() OVERRIDE;
130   virtual void OnLinkClicked(WindowOpenDisposition disposition) OVERRIDE;
131
132   std::string last_email_;
133   std::string email_;
134   Callback callback_;
135
136   // Web contents from which the "Learn more" link should be opened.
137   content::WebContents* web_contents_;
138
139   DISALLOW_COPY_AND_ASSIGN(ConfirmEmailDialogDelegate);
140 };
141
142 // static
143 void ConfirmEmailDialogDelegate::AskForConfirmation(
144     content::WebContents* contents,
145     const std::string& last_email,
146     const std::string& email,
147     Callback callback) {
148   TabModalConfirmDialog::Create(
149       new ConfirmEmailDialogDelegate(contents, last_email, email,
150                                      callback), contents);
151 }
152
153 ConfirmEmailDialogDelegate::ConfirmEmailDialogDelegate(
154     content::WebContents* contents,
155     const std::string& last_email,
156     const std::string& email,
157     Callback callback)
158   : TabModalConfirmDialogDelegate(contents),
159     last_email_(last_email),
160     email_(email),
161     callback_(callback),
162     web_contents_(contents) {
163 }
164
165 ConfirmEmailDialogDelegate::~ConfirmEmailDialogDelegate() {
166 }
167
168 base::string16 ConfirmEmailDialogDelegate::GetTitle() {
169   return l10n_util::GetStringUTF16(
170       IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_TITLE);
171 }
172
173 base::string16 ConfirmEmailDialogDelegate::GetDialogMessage() {
174   return l10n_util::GetStringFUTF16(
175       IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE,
176       base::UTF8ToUTF16(last_email_), base::UTF8ToUTF16(email_));
177 }
178
179 base::string16 ConfirmEmailDialogDelegate::GetAcceptButtonTitle() {
180   return l10n_util::GetStringUTF16(
181       IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_OK_BUTTON);
182 }
183
184 base::string16 ConfirmEmailDialogDelegate::GetCancelButtonTitle() {
185   return l10n_util::GetStringUTF16(
186       IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON);
187 }
188
189 base::string16 ConfirmEmailDialogDelegate::GetLinkText() const {
190   return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
191 }
192
193 void ConfirmEmailDialogDelegate::OnAccepted() {
194   base::ResetAndReturn(&callback_).Run(CREATE_NEW_USER);
195 }
196
197 void ConfirmEmailDialogDelegate::OnCanceled() {
198   base::ResetAndReturn(&callback_).Run(START_SYNC);
199 }
200
201 void ConfirmEmailDialogDelegate::OnClosed() {
202   base::ResetAndReturn(&callback_).Run(CLOSE);
203 }
204
205 void ConfirmEmailDialogDelegate::OnLinkClicked(
206     WindowOpenDisposition disposition) {
207   content::OpenURLParams params(
208       GURL(chrome::kChromeSyncMergeTroubleshootingURL),
209       content::Referrer(),
210       NEW_POPUP,
211       content::PAGE_TRANSITION_AUTO_TOPLEVEL,
212       false);
213   // It is guaranteed that |web_contents_| is valid here because when it's
214   // deleted, the dialog is immediately closed and no further action can be
215   // performed.
216   web_contents_->OpenURL(params);
217 }
218
219
220 // Helpers --------------------------------------------------------------------
221
222 // Add a specific email to the list of emails rejected for one-click
223 // sign-in, for this profile.
224 void AddEmailToOneClickRejectedList(Profile* profile,
225                                     const std::string& email) {
226   ListPrefUpdate updater(profile->GetPrefs(),
227                          prefs::kReverseAutologinRejectedEmailList);
228   updater->AppendIfNotPresent(new base::StringValue(email));
229 }
230
231 void LogOneClickHistogramValue(int action) {
232   UMA_HISTOGRAM_ENUMERATION("Signin.OneClickActions", action,
233                             one_click_signin::HISTOGRAM_MAX);
234   UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
235                             one_click_signin::HISTOGRAM_MAX);
236 }
237
238 void RedirectToNtpOrAppsPageWithIds(int child_id,
239                                     int route_id,
240                                     signin::Source source) {
241   content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
242                                                                     route_id);
243   if (!web_contents)
244     return;
245
246   OneClickSigninHelper::RedirectToNtpOrAppsPage(web_contents, source);
247 }
248
249 // Start syncing with the given user information.
250 void StartSync(const OneClickSigninHelper::StartSyncArgs& args,
251                OneClickSigninSyncStarter::StartSyncMode start_mode) {
252   if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) {
253     LogOneClickHistogramValue(one_click_signin::HISTOGRAM_UNDO);
254     return;
255   }
256
257   // The wrapper deletes itself once it's done.
258   OneClickSigninHelper::SyncStarterWrapper* wrapper =
259       new OneClickSigninHelper::SyncStarterWrapper(args, start_mode);
260   wrapper->Start();
261
262   int action = one_click_signin::HISTOGRAM_MAX;
263   switch (args.auto_accept) {
264     case OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT:
265       break;
266     case OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED:
267       action =
268           start_mode == OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS ?
269               one_click_signin::HISTOGRAM_AUTO_WITH_DEFAULTS :
270               one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
271       break;
272     case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE:
273       DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
274       action = one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
275       break;
276     default:
277       NOTREACHED() << "Invalid auto_accept: " << args.auto_accept;
278       break;
279   }
280   if (action != one_click_signin::HISTOGRAM_MAX)
281     LogOneClickHistogramValue(action);
282 }
283
284 void StartExplicitSync(const OneClickSigninHelper::StartSyncArgs& args,
285                        content::WebContents* contents,
286                        OneClickSigninSyncStarter::StartSyncMode start_mode,
287                        ConfirmEmailDialogDelegate::Action action) {
288   bool enable_inline = !switches::IsEnableWebBasedSignin();
289   if (action == ConfirmEmailDialogDelegate::START_SYNC) {
290     StartSync(args, start_mode);
291     if (!enable_inline) {
292       // Redirect/tab closing for inline flow is handled by the sync callback.
293       OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
294           contents, args.source);
295     }
296   } else {
297     // Perform a redirection to the NTP/Apps page to hide the blank page when
298     // the action is CLOSE or CREATE_NEW_USER. The redirection is useful when
299     // the action is CREATE_NEW_USER because the "Create new user" page might
300     // be opened in a different tab that is already showing settings.
301     if (enable_inline) {
302       // Redirect/tab closing for inline flow is handled by the sync callback.
303       args.callback.Run(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
304     } else {
305       // Don't redirect when the visible URL is not a blank page: if the
306       // source is SOURCE_WEBSTORE_INSTALL, |contents| might be showing an app
307       // page that shouldn't be hidden.
308       //
309       // If redirecting, don't do so immediately, otherwise there may be 2
310       // nested navigations and a crash would occur (crbug.com/293261).  Post
311       // the task to the current thread instead.
312       if (signin::IsContinueUrlForWebBasedSigninFlow(
313               contents->GetVisibleURL())) {
314         base::MessageLoopProxy::current()->PostNonNestableTask(
315             FROM_HERE,
316             base::Bind(RedirectToNtpOrAppsPageWithIds,
317                        contents->GetRenderProcessHost()->GetID(),
318                        contents->GetRoutingID(),
319                        args.source));
320       }
321     }
322     if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) {
323       chrome::ShowSettingsSubPage(args.browser,
324                                   std::string(chrome::kCreateProfileSubPage));
325     }
326   }
327 }
328
329 void ClearPendingEmailOnIOThread(content::ResourceContext* context) {
330   ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
331   DCHECK(io_data);
332   io_data->set_reverse_autologin_pending_email(std::string());
333 }
334
335 // Determines the source of the sign in and the continue URL.  It's either one
336 // of the known sign-in access points (first run, NTP, Apps page, menu, or
337 // settings) or it's an implicit sign in via another Google property.  In the
338 // former case, "service" is also checked to make sure its "chromiumsync".
339 signin::Source GetSigninSource(const GURL& url, GURL* continue_url) {
340   DCHECK(url.is_valid());
341   std::string value;
342   net::GetValueForKeyInQuery(url, "service", &value);
343   bool possibly_an_explicit_signin = value == "chromiumsync";
344
345   // Find the final continue URL for this sign in.  In some cases, Gaia can
346   // continue to itself, with the original continue URL buried under a couple
347   // of layers of indirection.  Peel those layers away.  The final destination
348   // can also be "IsGaiaSignonRealm" so stop if we get to the end (but be sure
349   // we always extract at least one "continue" value).
350   GURL local_continue_url = signin::GetNextPageURLForPromoURL(url);
351   while (gaia::IsGaiaSignonRealm(local_continue_url.GetOrigin())) {
352     GURL next_continue_url =
353         signin::GetNextPageURLForPromoURL(local_continue_url);
354     if (!next_continue_url.is_valid())
355       break;
356     local_continue_url = next_continue_url;
357   }
358
359   if (continue_url && local_continue_url.is_valid()) {
360     DCHECK(!continue_url->is_valid() || *continue_url == local_continue_url);
361     *continue_url = local_continue_url;
362   }
363
364   return possibly_an_explicit_signin ?
365       signin::GetSourceForPromoURL(local_continue_url) :
366       signin::SOURCE_UNKNOWN;
367 }
368
369 // Returns true if |url| is a valid URL that can occur during the sign in
370 // process.  Valid URLs are of the form:
371 //
372 //    https://accounts.google.{TLD}/...
373 //    https://accounts.youtube.com/...
374 //    https://accounts.blogger.com/...
375 //
376 // All special headers used by one click sign in occur on
377 // https://accounts.google.com URLs.  However, the sign in process may redirect
378 // to intermediate Gaia URLs that do not end with .com.  For example, an account
379 // that uses SMS 2-factor outside the US may redirect to country specific URLs.
380 //
381 // The sign in process may also redirect to youtube and blogger account URLs
382 // so that Gaia acts as a single signon service.
383 bool IsValidGaiaSigninRedirectOrResponseURL(const GURL& url) {
384   std::string hostname = url.host();
385   if (google_util::IsGoogleHostname(hostname, google_util::ALLOW_SUBDOMAIN)) {
386     // Also using IsGaiaSignonRealm() to handle overriding with command line.
387     return gaia::IsGaiaSignonRealm(url.GetOrigin()) ||
388         StartsWithASCII(hostname, "accounts.", false);
389   }
390
391   GURL origin = url.GetOrigin();
392   if (origin == GURL("https://accounts.youtube.com") ||
393       origin == GURL("https://accounts.blogger.com"))
394     return true;
395
396   return false;
397 }
398
399 // Tells when we are in the process of showing either the signin to chrome page
400 // or the one click sign in to chrome page.
401 // NOTE: This should only be used for logging purposes since it relies on hard
402 // coded URLs that could change.
403 bool AreWeShowingSignin(GURL url, signin::Source source, std::string email) {
404   GURL::Replacements replacements;
405   replacements.ClearQuery();
406   GURL clean_login_url =
407       GaiaUrls::GetInstance()->service_login_url().ReplaceComponents(
408           replacements);
409
410   return (url.ReplaceComponents(replacements) == clean_login_url &&
411           source != signin::SOURCE_UNKNOWN) ||
412       (IsValidGaiaSigninRedirectOrResponseURL(url) &&
413        url.spec().find("ChromeLoginPrompt") != std::string::npos &&
414        !email.empty());
415 }
416
417 // CurrentHistoryCleaner ------------------------------------------------------
418
419 // Watch a webcontents and remove URL from the history once loading is complete.
420 // We have to delay the cleaning until the new URL has finished loading because
421 // we're not allowed to remove the last-loaded URL from the history.  Objects
422 // of this type automatically self-destruct once they're finished their work.
423 class CurrentHistoryCleaner : public content::WebContentsObserver {
424  public:
425   explicit CurrentHistoryCleaner(content::WebContents* contents);
426   virtual ~CurrentHistoryCleaner();
427
428   // content::WebContentsObserver:
429   virtual void WebContentsDestroyed(content::WebContents* contents) OVERRIDE;
430   virtual void DidCommitProvisionalLoadForFrame(
431       int64 frame_id,
432       const base::string16& frame_unique_name,
433       bool is_main_frame,
434       const GURL& url,
435       content::PageTransition transition_type,
436       content::RenderViewHost* render_view_host) OVERRIDE;
437
438  private:
439   scoped_ptr<content::WebContents> contents_;
440   int history_index_to_remove_;
441
442   DISALLOW_COPY_AND_ASSIGN(CurrentHistoryCleaner);
443 };
444
445 CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents)
446     : WebContentsObserver(contents) {
447   history_index_to_remove_ =
448       web_contents()->GetController().GetLastCommittedEntryIndex();
449 }
450
451 CurrentHistoryCleaner::~CurrentHistoryCleaner() {
452 }
453
454 void CurrentHistoryCleaner::DidCommitProvisionalLoadForFrame(
455     int64 frame_id,
456     const base::string16& frame_unique_name,
457     bool is_main_frame,
458     const GURL& url,
459     content::PageTransition transition_type,
460     content::RenderViewHost* render_view_host) {
461   // Return early if this is not top-level navigation.
462   if (!is_main_frame)
463     return;
464
465   content::NavigationController* nc = &web_contents()->GetController();
466   HistoryService* hs = HistoryServiceFactory::GetForProfile(
467       Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
468       Profile::IMPLICIT_ACCESS);
469
470   // Have to wait until something else gets added to history before removal.
471   if (history_index_to_remove_ < nc->GetLastCommittedEntryIndex()) {
472     content::NavigationEntry* entry =
473         nc->GetEntryAtIndex(history_index_to_remove_);
474     if (signin::IsContinueUrlForWebBasedSigninFlow(entry->GetURL())) {
475       hs->DeleteURL(entry->GetURL());
476       nc->RemoveEntryAtIndex(history_index_to_remove_);
477       delete this;  // Success.
478     }
479   }
480 }
481
482 void CurrentHistoryCleaner::WebContentsDestroyed(
483     content::WebContents* contents) {
484   delete this;  // Failure.
485 }
486
487 void CloseTab(content::WebContents* tab) {
488   content::WebContentsDelegate* tab_delegate = tab->GetDelegate();
489   if (tab_delegate)
490     tab_delegate->CloseContents(tab);
491 }
492
493 }  // namespace
494
495
496 // StartSyncArgs --------------------------------------------------------------
497
498 OneClickSigninHelper::StartSyncArgs::StartSyncArgs()
499     : profile(NULL),
500       browser(NULL),
501       auto_accept(AUTO_ACCEPT_NONE),
502       web_contents(NULL),
503       confirmation_required(OneClickSigninSyncStarter::NO_CONFIRMATION),
504       source(signin::SOURCE_UNKNOWN) {}
505
506 OneClickSigninHelper::StartSyncArgs::StartSyncArgs(
507     Profile* profile,
508     Browser* browser,
509     OneClickSigninHelper::AutoAccept auto_accept,
510     const std::string& session_index,
511     const std::string& email,
512     const std::string& password,
513     const std::string& refresh_token,
514     content::WebContents* web_contents,
515     bool untrusted_confirmation_required,
516     signin::Source source,
517     OneClickSigninSyncStarter::Callback callback)
518     : profile(profile),
519       browser(browser),
520       auto_accept(auto_accept),
521       session_index(session_index),
522       email(email),
523       password(password),
524       refresh_token(refresh_token),
525       web_contents(web_contents),
526       source(source),
527       callback(callback) {
528   DCHECK(session_index.empty() != refresh_token.empty());
529   if (untrusted_confirmation_required) {
530     confirmation_required = OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN;
531   } else if (source == signin::SOURCE_SETTINGS ||
532              source == signin::SOURCE_WEBSTORE_INSTALL) {
533     // Do not display a status confirmation for webstore installs or re-auth.
534     confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION;
535   } else {
536     confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
537   }
538 }
539
540 OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {}
541
542 // SyncStarterWrapper ---------------------------------------------------------
543
544 OneClickSigninHelper::SyncStarterWrapper::SyncStarterWrapper(
545       const OneClickSigninHelper::StartSyncArgs& args,
546       OneClickSigninSyncStarter::StartSyncMode start_mode)
547     : args_(args), start_mode_(start_mode), weak_pointer_factory_(this) {
548   BrowserList::AddObserver(this);
549
550   // Cache the parent desktop for the browser, so we can reuse that same
551   // desktop for any UI we want to display.
552   desktop_type_ = args_.browser ? args_.browser->host_desktop_type()
553                                 : chrome::GetActiveDesktop();
554 }
555
556 OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() {
557   BrowserList::RemoveObserver(this);
558 }
559
560 void OneClickSigninHelper::SyncStarterWrapper::Start() {
561   if (args_.refresh_token.empty()) {
562     if (args_.password.empty()) {
563       VerifyGaiaCookiesBeforeSignIn();
564     } else {
565       StartSigninOAuthHelper();
566     }
567   } else {
568     OnSigninOAuthInformationAvailable(args_.email, args_.email,
569                                       args_.refresh_token);
570   }
571 }
572
573 void
574 OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationAvailable(
575     const std::string& email,
576     const std::string& display_email,
577     const std::string& refresh_token) {
578   if (!gaia::AreEmailsSame(display_email, args_.email)) {
579     DisplayErrorBubble(
580         GoogleServiceAuthError(
581             GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
582   } else {
583     StartOneClickSigninSyncStarter(email, refresh_token);
584   }
585
586   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
587 }
588
589 void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure(
590   const GoogleServiceAuthError& error) {
591   DisplayErrorBubble(error.ToString());
592   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
593 }
594
595 void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved(
596     Browser* browser) {
597   if (args_.browser == browser)
598     args_.browser = NULL;
599 }
600
601 void OneClickSigninHelper::SyncStarterWrapper::VerifyGaiaCookiesBeforeSignIn() {
602   scoped_refptr<SigninManagerCookieHelper> cookie_helper(
603       new SigninManagerCookieHelper(
604           args_.profile->GetRequestContext(),
605           content::BrowserThread::GetMessageLoopProxyForThread(
606               content::BrowserThread::UI),
607           content::BrowserThread::GetMessageLoopProxyForThread(
608               content::BrowserThread::IO)));
609   cookie_helper->StartFetchingGaiaCookiesOnUIThread(
610       base::Bind(&SyncStarterWrapper::OnGaiaCookiesFetched,
611                  weak_pointer_factory_.GetWeakPtr(),
612                  args_.session_index));
613 }
614
615 void OneClickSigninHelper::SyncStarterWrapper::OnGaiaCookiesFetched(
616     const std::string session_index, const net::CookieList& cookie_list) {
617   net::CookieList::const_iterator it;
618   bool success = false;
619   for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
620     // Make sure the LSID cookie is set on the GAIA host, instead of a super-
621     // domain.
622     if (it->Name() == "LSID") {
623       if (it->IsHostCookie() && it->IsHttpOnly() && it->IsSecure()) {
624         // Found a valid LSID cookie. Continue loop to make sure we don't have
625         // invalid LSID cookies on any super-domain.
626         success = true;
627       } else {
628         success = false;
629         break;
630       }
631     }
632   }
633
634   if (success) {
635     StartSigninOAuthHelper();
636   } else {
637     DisplayErrorBubble(
638         GoogleServiceAuthError(
639             GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
640     base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
641   }
642 }
643
644 void OneClickSigninHelper::SyncStarterWrapper::DisplayErrorBubble(
645     const std::string& error_message) {
646   args_.browser = OneClickSigninSyncStarter::EnsureBrowser(
647       args_.browser, args_.profile, desktop_type_);
648   args_.browser->window()->ShowOneClickSigninBubble(
649       BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE,
650       base::string16(),  // No email required - this is not a SAML confirmation.
651       base::UTF8ToUTF16(error_message),
652       // Callback is ignored.
653       BrowserWindow::StartSyncCallback());
654 }
655
656 void OneClickSigninHelper::SyncStarterWrapper::StartSigninOAuthHelper() {
657   signin_oauth_helper_.reset(
658       new SigninOAuthHelper(args_.profile->GetRequestContext(),
659                             args_.session_index, this));
660 }
661
662 void
663 OneClickSigninHelper::SyncStarterWrapper::StartOneClickSigninSyncStarter(
664     const std::string& email,
665     const std::string& refresh_token) {
666   // The starter deletes itself once it's done.
667   new OneClickSigninSyncStarter(args_.profile, args_.browser,
668                                 email, args_.password,
669                                 refresh_token, start_mode_,
670                                 args_.web_contents,
671                                 args_.confirmation_required,
672                                 args_.callback);
673 }
674
675
676 // OneClickSigninHelper -------------------------------------------------------
677
678 DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper);
679
680 // static
681 const int OneClickSigninHelper::kMaxNavigationsSince = 10;
682
683 OneClickSigninHelper::OneClickSigninHelper(content::WebContents* web_contents,
684                                            PasswordManager* password_manager)
685     : content::WebContentsObserver(web_contents),
686       showing_signin_(false),
687       auto_accept_(AUTO_ACCEPT_NONE),
688       source_(signin::SOURCE_UNKNOWN),
689       switched_to_advanced_(false),
690       untrusted_navigations_since_signin_visit_(0),
691       untrusted_confirmation_required_(false),
692       do_not_clear_pending_email_(false),
693       do_not_start_sync_for_testing_(false),
694       weak_pointer_factory_(this) {
695   // May be NULL during testing.
696   if (password_manager) {
697     password_manager->AddSubmissionCallback(
698         base::Bind(&OneClickSigninHelper::PasswordSubmitted,
699                    weak_pointer_factory_.GetWeakPtr()));
700   }
701 }
702
703 OneClickSigninHelper::~OneClickSigninHelper() {
704   // WebContentsDestroyed() should always be called before the object is
705   // deleted.
706   DCHECK(!web_contents());
707 }
708
709 // static
710 void OneClickSigninHelper::LogHistogramValue(
711     signin::Source source, int action) {
712   switch (source) {
713     case signin::SOURCE_START_PAGE:
714       UMA_HISTOGRAM_ENUMERATION("Signin.StartPageActions", action,
715                                 one_click_signin::HISTOGRAM_MAX);
716       break;
717     case signin::SOURCE_NTP_LINK:
718       UMA_HISTOGRAM_ENUMERATION("Signin.NTPLinkActions", action,
719                                 one_click_signin::HISTOGRAM_MAX);
720       break;
721     case signin::SOURCE_MENU:
722       UMA_HISTOGRAM_ENUMERATION("Signin.MenuActions", action,
723                                 one_click_signin::HISTOGRAM_MAX);
724       break;
725     case signin::SOURCE_SETTINGS:
726       UMA_HISTOGRAM_ENUMERATION("Signin.SettingsActions", action,
727                                 one_click_signin::HISTOGRAM_MAX);
728       break;
729     case signin::SOURCE_EXTENSION_INSTALL_BUBBLE:
730       UMA_HISTOGRAM_ENUMERATION("Signin.ExtensionInstallBubbleActions", action,
731                                 one_click_signin::HISTOGRAM_MAX);
732       break;
733     case signin::SOURCE_WEBSTORE_INSTALL:
734       UMA_HISTOGRAM_ENUMERATION("Signin.WebstoreInstallActions", action,
735                                 one_click_signin::HISTOGRAM_MAX);
736       break;
737     case signin::SOURCE_APP_LAUNCHER:
738       UMA_HISTOGRAM_ENUMERATION("Signin.AppLauncherActions", action,
739                                 one_click_signin::HISTOGRAM_MAX);
740       break;
741     case signin::SOURCE_APPS_PAGE_LINK:
742       UMA_HISTOGRAM_ENUMERATION("Signin.AppsPageLinkActions", action,
743                                 one_click_signin::HISTOGRAM_MAX);
744       break;
745     case signin::SOURCE_BOOKMARK_BUBBLE:
746       UMA_HISTOGRAM_ENUMERATION("Signin.BookmarkBubbleActions", action,
747                                 one_click_signin::HISTOGRAM_MAX);
748       break;
749     case signin::SOURCE_AVATAR_BUBBLE_SIGN_IN:
750       UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action,
751                                 one_click_signin::HISTOGRAM_MAX);
752       break;
753     case signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT:
754       UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action,
755                                 one_click_signin::HISTOGRAM_MAX);
756       break;
757     case signin::SOURCE_DEVICES_PAGE:
758       UMA_HISTOGRAM_ENUMERATION("Signin.DevicesPageActions", action,
759                                 one_click_signin::HISTOGRAM_MAX);
760     default:
761       // This switch statement needs to be updated when the enum Source changes.
762       COMPILE_ASSERT(signin::SOURCE_UNKNOWN == 12,
763                      kSourceEnumHasChangedButNotThisSwitchStatement);
764       NOTREACHED();
765       return;
766   }
767   UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
768                             one_click_signin::HISTOGRAM_MAX);
769 }
770
771 // static
772 void OneClickSigninHelper::CreateForWebContentsWithPasswordManager(
773     content::WebContents* contents,
774     PasswordManager* password_manager) {
775   if (!FromWebContents(contents)) {
776     contents->SetUserData(UserDataKey(),
777                           new OneClickSigninHelper(contents, password_manager));
778   }
779 }
780
781 // static
782 bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents,
783                                     CanOfferFor can_offer_for,
784                                     const std::string& email,
785                                     std::string* error_message) {
786   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
787     VLOG(1) << "OneClickSigninHelper::CanOffer";
788
789   if (error_message)
790     error_message->clear();
791
792   if (!web_contents)
793     return false;
794
795   if (web_contents->GetBrowserContext()->IsOffTheRecord())
796     return false;
797
798   Profile* profile =
799       Profile::FromBrowserContext(web_contents->GetBrowserContext());
800   if (!profile)
801     return false;
802
803   SigninManager* manager =
804       SigninManagerFactory::GetForProfile(profile);
805   if (manager && !manager->IsSigninAllowed())
806     return false;
807
808   if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY &&
809       !profile->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled))
810     return false;
811
812   if (!ChromeSigninClient::ProfileAllowsSigninCookies(profile))
813     return false;
814
815   if (!email.empty()) {
816     if (!manager)
817       return false;
818
819     // Make sure this username is not prohibited by policy.
820     if (!manager->IsAllowedUsername(email)) {
821       if (error_message) {
822         error_message->assign(
823             l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED));
824       }
825       return false;
826     }
827
828     if (can_offer_for != CAN_OFFER_FOR_SECONDARY_ACCOUNT) {
829       // If the signin manager already has an authenticated name, then this is a
830       // re-auth scenario.  Make sure the email just signed in corresponds to
831       // the one sign in manager expects.
832       std::string current_email = manager->GetAuthenticatedUsername();
833       const bool same_email = gaia::AreEmailsSame(current_email, email);
834       if (!current_email.empty() && !same_email) {
835         UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
836                                   signin::HISTOGRAM_ACCOUNT_MISSMATCH,
837                                   signin::HISTOGRAM_MAX);
838         if (error_message) {
839           error_message->assign(
840               l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL,
841                                         base::UTF8ToUTF16(current_email)));
842         }
843         return false;
844       }
845
846       // If some profile, not just the current one, is already connected to this
847       // account, don't show the infobar.
848       if (g_browser_process && !same_email) {
849         ProfileManager* manager = g_browser_process->profile_manager();
850         if (manager) {
851           ProfileInfoCache& cache = manager->GetProfileInfoCache();
852           for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) {
853             std::string current_email =
854                 base::UTF16ToUTF8(cache.GetUserNameOfProfileAtIndex(i));
855             if (gaia::AreEmailsSame(email, current_email)) {
856               if (error_message) {
857                 error_message->assign(
858                     l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR));
859               }
860               return false;
861             }
862           }
863         }
864       }
865     }
866
867     // If email was already rejected by this profile for one-click sign-in.
868     if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY) {
869       const base::ListValue* rejected_emails = profile->GetPrefs()->GetList(
870           prefs::kReverseAutologinRejectedEmailList);
871       if (!rejected_emails->empty()) {
872         base::ListValue::const_iterator iter = rejected_emails->Find(
873             base::StringValue(email));
874         if (iter != rejected_emails->end())
875           return false;
876       }
877     }
878   }
879
880   VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can";
881   return true;
882 }
883
884 // static
885 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread(
886     net::URLRequest* request,
887     ProfileIOData* io_data) {
888   return CanOfferOnIOThreadImpl(request->url(), request, io_data);
889 }
890
891 // static
892 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThreadImpl(
893     const GURL& url,
894     base::SupportsUserData* request,
895     ProfileIOData* io_data) {
896   if (!gaia::IsGaiaSignonRealm(url.GetOrigin()))
897     return IGNORE_REQUEST;
898
899   if (!io_data)
900     return DONT_OFFER;
901
902   // Check for incognito before other parts of the io_data, since those
903   // members may not be initalized.
904   if (io_data->IsOffTheRecord())
905     return DONT_OFFER;
906
907   if (!io_data->signin_allowed()->GetValue())
908     return DONT_OFFER;
909
910   if (!io_data->reverse_autologin_enabled()->GetValue())
911     return DONT_OFFER;
912
913   if (!io_data->google_services_username()->GetValue().empty())
914     return DONT_OFFER;
915
916   if (!ChromeSigninClient::SettingsAllowSigninCookies(
917           io_data->GetCookieSettings()))
918     return DONT_OFFER;
919
920   // The checks below depend on chrome already knowing what account the user
921   // signed in with.  This happens only after receiving the response containing
922   // the Google-Accounts-SignIn header.  Until then, if there is even a chance
923   // that we want to connect the profile, chrome needs to tell Gaia that
924   // it should offer the interstitial.  Therefore missing one click data on
925   // the request means can offer is true.
926   const std::string& pending_email = io_data->reverse_autologin_pending_email();
927   if (!pending_email.empty()) {
928     if (!SigninManager::IsUsernameAllowedByPolicy(pending_email,
929             io_data->google_services_username_pattern()->GetValue())) {
930       return DONT_OFFER;
931     }
932
933     std::vector<std::string> rejected_emails =
934         io_data->one_click_signin_rejected_email_list()->GetValue();
935     if (std::count_if(rejected_emails.begin(), rejected_emails.end(),
936                       std::bind2nd(std::equal_to<std::string>(),
937                                    pending_email)) > 0) {
938       return DONT_OFFER;
939     }
940
941     if (io_data->signin_names()->GetEmails().count(
942             base::UTF8ToUTF16(pending_email)) > 0) {
943       return DONT_OFFER;
944     }
945   }
946
947   return CAN_OFFER;
948 }
949
950 // static
951 void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest* request,
952                                                  ProfileIOData* io_data,
953                                                  int child_id,
954                                                  int route_id) {
955   std::string google_chrome_signin_value;
956   std::string google_accounts_signin_value;
957   request->GetResponseHeaderByName("Google-Chrome-SignIn",
958                                    &google_chrome_signin_value);
959   request->GetResponseHeaderByName("Google-Accounts-SignIn",
960                                    &google_accounts_signin_value);
961
962   if (!google_accounts_signin_value.empty() ||
963       !google_chrome_signin_value.empty()) {
964     VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
965             << " g-a-s='" << google_accounts_signin_value << "'"
966             << " g-c-s='" << google_chrome_signin_value << "'";
967   }
968
969   if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin()))
970     return;
971
972   // Parse Google-Accounts-SignIn.
973   std::vector<std::pair<std::string, std::string> > pairs;
974   base::SplitStringIntoKeyValuePairs(google_accounts_signin_value, '=', ',',
975                                      &pairs);
976   std::string session_index;
977   std::string email;
978   for (size_t i = 0; i < pairs.size(); ++i) {
979     const std::pair<std::string, std::string>& pair = pairs[i];
980     const std::string& key = pair.first;
981     const std::string& value = pair.second;
982     if (key == "email") {
983       base::TrimString(value, "\"", &email);
984     } else if (key == "sessionindex") {
985       session_index = value;
986     }
987   }
988
989   // Later in the chain of this request, we'll need to check the email address
990   // in the IO thread (see CanOfferOnIOThread).  So save the email address as
991   // user data on the request (only for web-based flow).
992   if (!email.empty())
993     io_data->set_reverse_autologin_pending_email(email);
994
995   if (!email.empty() || !session_index.empty()) {
996     VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
997             << " email=" << email
998             << " sessionindex=" << session_index;
999   }
1000
1001   // Parse Google-Chrome-SignIn.
1002   AutoAccept auto_accept = AUTO_ACCEPT_NONE;
1003   signin::Source source = signin::SOURCE_UNKNOWN;
1004   GURL continue_url;
1005   std::vector<std::string> tokens;
1006   base::SplitString(google_chrome_signin_value, ',', &tokens);
1007   for (size_t i = 0; i < tokens.size(); ++i) {
1008     const std::string& token = tokens[i];
1009     if (token == "accepted") {
1010       auto_accept = AUTO_ACCEPT_ACCEPTED;
1011     } else if (token == "configure") {
1012       auto_accept = AUTO_ACCEPT_CONFIGURE;
1013     } else if (token == "rejected-for-profile") {
1014       auto_accept = AUTO_ACCEPT_REJECTED_FOR_PROFILE;
1015     }
1016   }
1017
1018   // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu,
1019   // settings) then force the auto accept type to explicit.
1020   source = GetSigninSource(request->url(), &continue_url);
1021   if (source != signin::SOURCE_UNKNOWN)
1022     auto_accept = AUTO_ACCEPT_EXPLICIT;
1023
1024   if (auto_accept != AUTO_ACCEPT_NONE) {
1025     VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
1026             << " auto_accept=" << auto_accept;
1027   }
1028
1029   // If |session_index|, |email|, |auto_accept|, and |continue_url| all have
1030   // their default value, don't bother posting a task to the UI thread.
1031   // It will be a noop anyway.
1032   //
1033   // The two headers above may (but not always) come in different http requests
1034   // so a post to the UI thread is still needed if |auto_accept| is not its
1035   // default value, but |email| and |session_index| are.
1036   if (session_index.empty() && email.empty() &&
1037       auto_accept == AUTO_ACCEPT_NONE && !continue_url.is_valid()) {
1038     return;
1039   }
1040
1041   content::BrowserThread::PostTask(
1042       content::BrowserThread::UI, FROM_HERE,
1043       base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread, session_index,
1044                  email, auto_accept, source, continue_url, child_id, route_id));
1045 }
1046
1047 // static
1048 void OneClickSigninHelper::LogConfirmHistogramValue(int action) {
1049   UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action,
1050                             one_click_signin::HISTOGRAM_CONFIRM_MAX);
1051 }
1052 // static
1053 void OneClickSigninHelper::ShowInfoBarUIThread(
1054     const std::string& session_index,
1055     const std::string& email,
1056     AutoAccept auto_accept,
1057     signin::Source source,
1058     const GURL& continue_url,
1059     int child_id,
1060     int route_id) {
1061   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
1062
1063   content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
1064                                                                     route_id);
1065   if (!web_contents)
1066     return;
1067
1068   // TODO(mathp): The appearance of this infobar should be tested using a
1069   // browser_test.
1070   OneClickSigninHelper* helper =
1071       OneClickSigninHelper::FromWebContents(web_contents);
1072   if (!helper)
1073     return;
1074
1075   if (auto_accept != AUTO_ACCEPT_NONE)
1076     helper->auto_accept_ = auto_accept;
1077
1078   if (source != signin::SOURCE_UNKNOWN &&
1079       helper->source_ == signin::SOURCE_UNKNOWN) {
1080     helper->source_ = source;
1081   }
1082
1083   // Save the email in the one-click signin manager.  The manager may
1084   // not exist if the contents is incognito or if the profile is already
1085   // connected to a Google account.
1086   if (!session_index.empty())
1087     helper->session_index_ = session_index;
1088
1089   if (!email.empty())
1090     helper->email_ = email;
1091
1092   CanOfferFor can_offer_for =
1093       (auto_accept != AUTO_ACCEPT_EXPLICIT &&
1094           helper->auto_accept_ != AUTO_ACCEPT_EXPLICIT) ?
1095           CAN_OFFER_FOR_INTERSTITAL_ONLY : CAN_OFFER_FOR_ALL;
1096
1097   std::string error_message;
1098
1099   if (!web_contents || !CanOffer(web_contents, can_offer_for, email,
1100                                  &error_message)) {
1101     VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering";
1102     // TODO(rogerta): Can we just display our error now instead of keeping it
1103     // around and doing it later?
1104     if (helper && helper->error_message_.empty() && !error_message.empty())
1105       helper->error_message_ = error_message;
1106
1107     return;
1108   }
1109
1110   // Only allow the dedicated signin process to sign the user into
1111   // Chrome without intervention, because it doesn't load any untrusted
1112   // pages.  If at any point an untrusted page is detected, chrome will
1113   // show a modal dialog asking the user to confirm.
1114   Profile* profile =
1115       Profile::FromBrowserContext(web_contents->GetBrowserContext());
1116   ChromeSigninClient* signin_client =
1117       profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
1118   helper->untrusted_confirmation_required_ |=
1119       (signin_client && !signin_client->IsSigninProcess(child_id));
1120
1121   if (continue_url.is_valid()) {
1122     // Set |original_continue_url_| if it is currently empty. |continue_url|
1123     // could be modified by gaia pages, thus we need to record the original
1124     // continue url to navigate back to the right page when sync setup is
1125     // complete.
1126     if (helper->original_continue_url_.is_empty())
1127       helper->original_continue_url_ = continue_url;
1128     helper->continue_url_ = continue_url;
1129   }
1130 }
1131
1132 // static
1133 void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem(
1134     content::WebContents* web_contents) {
1135   // Only actually remove the item if it's the blank.html continue url.
1136   if (signin::IsContinueUrlForWebBasedSigninFlow(
1137           web_contents->GetLastCommittedURL())) {
1138     new CurrentHistoryCleaner(web_contents);  // will self-destruct when done
1139   }
1140 }
1141
1142 // static
1143 void OneClickSigninHelper::ShowSigninErrorBubble(Browser* browser,
1144                                                  const std::string& error) {
1145   DCHECK(!error.empty());
1146
1147   browser->window()->ShowOneClickSigninBubble(
1148       BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE,
1149       base::string16(), /* no SAML email */
1150       base::UTF8ToUTF16(error),
1151       // This callback is never invoked.
1152       // TODO(rogerta): Separate out the bubble API so we don't have to pass
1153       // ignored |email| and |callback| params.
1154       BrowserWindow::StartSyncCallback());
1155 }
1156
1157 // static
1158 bool OneClickSigninHelper::HandleCrossAccountError(
1159     content::WebContents* contents,
1160     const std::string& session_index,
1161     const std::string& email,
1162     const std::string& password,
1163     const std::string& refresh_token,
1164     OneClickSigninHelper::AutoAccept auto_accept,
1165     signin::Source source,
1166     OneClickSigninSyncStarter::StartSyncMode start_mode,
1167     OneClickSigninSyncStarter::Callback sync_callback) {
1168   Profile* profile =
1169       Profile::FromBrowserContext(contents->GetBrowserContext());
1170   std::string last_email =
1171       profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername);
1172
1173   if (!last_email.empty() && !gaia::AreEmailsSame(last_email, email)) {
1174     // If the new email address is different from the email address that
1175     // just signed in, show a confirmation dialog.
1176
1177     // No need to display a second confirmation so pass false below.
1178     // TODO(atwilson): Move this into OneClickSigninSyncStarter.
1179     // The tab modal dialog always executes its callback before |contents|
1180     // is deleted.
1181     Browser* browser = chrome::FindBrowserWithWebContents(contents);
1182     ConfirmEmailDialogDelegate::AskForConfirmation(
1183         contents,
1184         last_email,
1185         email,
1186         base::Bind(
1187             &StartExplicitSync,
1188             StartSyncArgs(profile, browser, auto_accept,
1189                           session_index, email, password, refresh_token,
1190                           contents, false /* confirmation_required */, source,
1191                           sync_callback),
1192             contents,
1193             start_mode));
1194     return true;
1195   }
1196
1197   return false;
1198 }
1199
1200 // static
1201 void OneClickSigninHelper::RedirectToNtpOrAppsPage(
1202     content::WebContents* contents, signin::Source source) {
1203   VLOG(1) << "RedirectToNtpOrAppsPage";
1204   // Redirect to NTP/Apps page and display a confirmation bubble
1205   GURL url(source == signin::SOURCE_APPS_PAGE_LINK ?
1206            chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL);
1207   content::OpenURLParams params(url,
1208                                 content::Referrer(),
1209                                 CURRENT_TAB,
1210                                 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1211                                 false);
1212   contents->OpenURL(params);
1213 }
1214
1215 // static
1216 void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
1217     content::WebContents* contents, signin::Source source) {
1218   if (source != signin::SOURCE_SETTINGS &&
1219       source != signin::SOURCE_WEBSTORE_INSTALL) {
1220     RedirectToNtpOrAppsPage(contents, source);
1221   }
1222 }
1223
1224 void OneClickSigninHelper::RedirectToSignin() {
1225   VLOG(1) << "OneClickSigninHelper::RedirectToSignin";
1226
1227   // Extract the existing sounce=X value.  Default to "2" if missing.
1228   signin::Source source = signin::GetSourceForPromoURL(continue_url_);
1229   if (source == signin::SOURCE_UNKNOWN)
1230     source = signin::SOURCE_MENU;
1231   GURL page = signin::GetPromoURL(source, false);
1232
1233   content::WebContents* contents = web_contents();
1234   contents->GetController().LoadURL(page,
1235                                     content::Referrer(),
1236                                     content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1237                                     std::string());
1238 }
1239
1240 void OneClickSigninHelper::CleanTransientState() {
1241   VLOG(1) << "OneClickSigninHelper::CleanTransientState";
1242   showing_signin_ = false;
1243   email_.clear();
1244   password_.clear();
1245   auto_accept_ = AUTO_ACCEPT_NONE;
1246   source_ = signin::SOURCE_UNKNOWN;
1247   switched_to_advanced_ = false;
1248   continue_url_ = GURL();
1249   untrusted_navigations_since_signin_visit_ = 0;
1250   untrusted_confirmation_required_ = false;
1251   error_message_.clear();
1252
1253   // Post to IO thread to clear pending email.
1254   if (!do_not_clear_pending_email_) {
1255     Profile* profile =
1256         Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1257     content::BrowserThread::PostTask(
1258         content::BrowserThread::IO, FROM_HERE,
1259         base::Bind(&ClearPendingEmailOnIOThread,
1260                    base::Unretained(profile->GetResourceContext())));
1261   }
1262 }
1263
1264 void OneClickSigninHelper::PasswordSubmitted(
1265     const autofill::PasswordForm& form) {
1266   // We only need to scrape the password for Gaia logins.
1267   if (gaia::IsGaiaSignonRealm(GURL(form.signon_realm))) {
1268     VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password";
1269     password_ = base::UTF16ToUTF8(form.password_value);
1270   }
1271 }
1272
1273 void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() {
1274   do_not_clear_pending_email_ = true;
1275 }
1276
1277 void OneClickSigninHelper::set_do_not_start_sync_for_testing() {
1278   do_not_start_sync_for_testing_ = true;
1279 }
1280
1281 void OneClickSigninHelper::DidStartNavigationToPendingEntry(
1282     const GURL& url,
1283     content::NavigationController::ReloadType reload_type) {
1284   VLOG(1) << "OneClickSigninHelper::DidStartNavigationToPendingEntry: url=" <<
1285       url.spec();
1286   // If the tab navigates to a new page, and this page is not a valid Gaia
1287   // sign in redirect or reponse, or the expected continue URL, make sure to
1288   // clear the internal state.  This is needed to detect navigations in the
1289   // middle of the sign in process that may redirect back to the sign in
1290   // process (see crbug.com/181163 for details).
1291   GURL::Replacements replacements;
1292   replacements.ClearQuery();
1293
1294   if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1295       continue_url_.is_valid() &&
1296       url.ReplaceComponents(replacements) !=
1297           continue_url_.ReplaceComponents(replacements)) {
1298     if (++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince)
1299       CleanTransientState();
1300   }
1301 }
1302
1303 void OneClickSigninHelper::DidNavigateMainFrame(
1304     const content::LoadCommittedDetails& details,
1305     const content::FrameNavigateParams& params) {
1306   if (!SigninManager::IsWebBasedSigninFlowURL(params.url)) {
1307     // Make sure the renderer process is no longer considered the trusted
1308     // sign-in process when a navigation to a non-sign-in URL occurs.
1309     Profile* profile =
1310         Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1311     ChromeSigninClient* signin_client =
1312         profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
1313     int process_id = web_contents()->GetRenderProcessHost()->GetID();
1314     if (signin_client && signin_client->IsSigninProcess(process_id))
1315       signin_client->ClearSigninProcess();
1316
1317     // If the navigation to a non-sign-in URL hasn't been triggered by the web
1318     // contents, the sign in flow has been aborted and the state must be
1319     // cleaned (crbug.com/269421).
1320     if (!content::PageTransitionIsWebTriggerable(params.transition) &&
1321         auto_accept_ != AUTO_ACCEPT_NONE) {
1322       CleanTransientState();
1323     }
1324   }
1325 }
1326
1327 void OneClickSigninHelper::DidStopLoading(
1328     content::RenderViewHost* render_view_host) {
1329   // If the user left the sign in process, clear all members.
1330   // TODO(rogerta): might need to allow some youtube URLs.
1331   content::WebContents* contents = web_contents();
1332   const GURL url = contents->GetLastCommittedURL();
1333   Profile* profile =
1334       Profile::FromBrowserContext(contents->GetBrowserContext());
1335   VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url.spec();
1336
1337   if (url.scheme() == content::kChromeUIScheme) {
1338     // Suppresses OneClickSigninHelper on webUI pages to avoid inteference with
1339     // inline signin flows.
1340     VLOG(1) << "OneClickSigninHelper::DidStopLoading: suppressed for url="
1341             << url.spec();
1342     CleanTransientState();
1343     return;
1344   }
1345
1346   // If an error has already occured during the sign in flow, make sure to
1347   // display it to the user and abort the process.  Do this only for
1348   // explicit sign ins.
1349   // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()?
1350   if (!error_message_.empty() && auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1351     VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_;
1352     RemoveSigninRedirectURLHistoryItem(contents);
1353     // After we redirect to NTP, our browser pointer gets corrupted because the
1354     // WebContents have changed, so grab the browser pointer
1355     // before the navigation.
1356     Browser* browser = chrome::FindBrowserWithWebContents(contents);
1357
1358     // Redirect to the landing page and display an error popup.
1359     RedirectToNtpOrAppsPage(web_contents(), source_);
1360     ShowSigninErrorBubble(browser, error_message_);
1361     CleanTransientState();
1362     return;
1363   }
1364
1365   if (AreWeShowingSignin(url, source_, email_)) {
1366     if (!showing_signin_) {
1367       if (source_ == signin::SOURCE_UNKNOWN)
1368         LogOneClickHistogramValue(one_click_signin::HISTOGRAM_SHOWN);
1369       else
1370         LogHistogramValue(source_, one_click_signin::HISTOGRAM_SHOWN);
1371     }
1372     showing_signin_ = true;
1373   }
1374
1375   // When Gaia finally redirects to the continue URL, Gaia will add some
1376   // extra query parameters.  So ignore the parameters when checking to see
1377   // if the user has continued.  Sometimes locales will redirect to a country-
1378   // specific TLD so just make sure it's a valid domain instead of comparing
1379   // for an exact match.
1380   GURL::Replacements replacements;
1381   replacements.ClearQuery();
1382   bool google_domain_url = google_util::IsGoogleDomainUrl(
1383       url,
1384       google_util::ALLOW_SUBDOMAIN,
1385       google_util::DISALLOW_NON_STANDARD_PORTS);
1386   const bool continue_url_match =
1387       google_domain_url &&
1388       url.ReplaceComponents(replacements).path() ==
1389         continue_url_.ReplaceComponents(replacements).path();
1390   const bool original_continue_url_match =
1391       google_domain_url &&
1392       url.ReplaceComponents(replacements).path() ==
1393         original_continue_url_.ReplaceComponents(replacements).path();
1394
1395   if (continue_url_match)
1396     RemoveSigninRedirectURLHistoryItem(contents);
1397
1398   // If there is no valid email yet, there is nothing to do.  As of M26, the
1399   // password is allowed to be empty, since its no longer required to setup
1400   // sync.
1401   if (email_.empty()) {
1402     VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do";
1403     // Original-url check done because some user actions cans get us to a page
1404     // via a POST instead of a GET (and thus to immediate "cuntinue url") but
1405     // we still want redirects from the "blank.html" landing page to work for
1406     // non-security related redirects like NTP.
1407     // https://code.google.com/p/chromium/issues/detail?id=321938
1408     if (original_continue_url_match) {
1409       if (auto_accept_ == AUTO_ACCEPT_EXPLICIT)
1410         RedirectToSignin();
1411       std::string unused_value;
1412       if (net::GetValueForKeyInQuery(url, "ntp", &unused_value)) {
1413         signin::SetUserSkippedPromo(profile);
1414         RedirectToNtpOrAppsPage(web_contents(), source_);
1415       }
1416     } else {
1417       if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1418           ++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) {
1419         CleanTransientState();
1420       }
1421     }
1422
1423     return;
1424   }
1425
1426   if (!continue_url_match && IsValidGaiaSigninRedirectOrResponseURL(url))
1427     return;
1428
1429   // During an explicit sign in, if the user has not yet reached the final
1430   // continue URL, wait for it to arrive. Note that Gaia will add some extra
1431   // query parameters to the continue URL.  Ignore them when checking to
1432   // see if the user has continued.
1433   //
1434   // If this is not an explicit sign in, we don't need to check if we landed
1435   // on the right continue URL.  This is important because the continue URL
1436   // may itself lead to a redirect, which means this function will never see
1437   // the continue URL go by.
1438   if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1439     DCHECK(source_ != signin::SOURCE_UNKNOWN);
1440     if (!continue_url_match) {
1441       VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='"
1442               << url.spec()
1443               << "' expected continue url=" << continue_url_;
1444       CleanTransientState();
1445       return;
1446     }
1447
1448     // In explicit sign ins, the user may have changed the box
1449     // "Let me choose what to sync".  This is reflected as a change in the
1450     // source of the continue URL.  Make one last check of the current URL
1451     // to see if there is a valid source.  If so, it overrides the
1452     // current source.
1453     //
1454     // If the source was changed to SOURCE_SETTINGS, we want
1455     // OneClickSigninSyncStarter to reuse the current tab to display the
1456     // advanced configuration.
1457     signin::Source source = signin::GetSourceForPromoURL(url);
1458     if (source != source_) {
1459       source_ = source;
1460       switched_to_advanced_ = source == signin::SOURCE_SETTINGS;
1461     }
1462   }
1463
1464   Browser* browser = chrome::FindBrowserWithWebContents(contents);
1465
1466   VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go."
1467           << " auto_accept=" << auto_accept_
1468           << " source=" << source_;
1469
1470   switch (auto_accept_) {
1471     case AUTO_ACCEPT_NONE:
1472       if (showing_signin_)
1473         LogOneClickHistogramValue(one_click_signin::HISTOGRAM_DISMISSED);
1474       break;
1475     case AUTO_ACCEPT_ACCEPTED:
1476       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
1477       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_DEFAULTS);
1478       SigninManager::DisableOneClickSignIn(profile);
1479       // Start syncing with the default settings - prompt the user to sign in
1480       // first.
1481       if (!do_not_start_sync_for_testing_) {
1482         StartSync(
1483             StartSyncArgs(profile, browser, auto_accept_,
1484                           session_index_, email_, password_, "",
1485                           NULL  /* don't force sync setup in same tab */,
1486                           true  /* confirmation_required */, source_,
1487                           CreateSyncStarterCallback()),
1488             OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
1489       }
1490       break;
1491     case AUTO_ACCEPT_CONFIGURE:
1492       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
1493       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_ADVANCED);
1494       SigninManager::DisableOneClickSignIn(profile);
1495       // Display the extra confirmation (even in the SAML case) in case this
1496       // was an untrusted renderer.
1497       if (!do_not_start_sync_for_testing_) {
1498         StartSync(
1499             StartSyncArgs(profile, browser, auto_accept_,
1500                           session_index_, email_, password_, "",
1501                           NULL  /* don't force sync setup in same tab */,
1502                           true  /* confirmation_required */, source_,
1503                           CreateSyncStarterCallback()),
1504             OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
1505       }
1506       break;
1507     case AUTO_ACCEPT_EXPLICIT: {
1508       signin::Source original_source =
1509           signin::GetSourceForPromoURL(original_continue_url_);
1510       if (switched_to_advanced_) {
1511         LogHistogramValue(original_source,
1512                           one_click_signin::HISTOGRAM_WITH_ADVANCED);
1513         LogHistogramValue(original_source,
1514                           one_click_signin::HISTOGRAM_ACCEPTED);
1515       } else {
1516         LogHistogramValue(source_, one_click_signin::HISTOGRAM_ACCEPTED);
1517         LogHistogramValue(source_, one_click_signin::HISTOGRAM_WITH_DEFAULTS);
1518       }
1519
1520       // - If sign in was initiated from the NTP or the hotdog menu, sync with
1521       //   default settings.
1522       // - If sign in was initiated from the settings page for first time sync
1523       //   set up, show the advanced sync settings dialog.
1524       // - If sign in was initiated from the settings page due to a re-auth when
1525       //   sync was already setup, simply navigate back to the settings page.
1526       ProfileSyncService* sync_service =
1527           ProfileSyncServiceFactory::GetForProfile(profile);
1528       SigninErrorController* error_controller =
1529           ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
1530               signin_error_controller();
1531
1532       OneClickSigninSyncStarter::StartSyncMode start_mode =
1533           source_ == signin::SOURCE_SETTINGS ?
1534               (error_controller->HasError() &&
1535                sync_service && sync_service->HasSyncSetupCompleted()) ?
1536                   OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
1537                   OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
1538               OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
1539
1540       if (!HandleCrossAccountError(contents, session_index_, email_, password_,
1541               "", auto_accept_, source_, start_mode,
1542               CreateSyncStarterCallback())) {
1543         if (!do_not_start_sync_for_testing_) {
1544           StartSync(
1545               StartSyncArgs(profile, browser, auto_accept_,
1546                             session_index_, email_, password_, "",
1547                             contents,
1548                             untrusted_confirmation_required_, source_,
1549                             CreateSyncStarterCallback()),
1550               start_mode);
1551         }
1552
1553         // If this explicit sign in is not from settings page/webstore, show
1554         // the NTP/Apps page after sign in completes. In the case of the
1555         // settings page, it will get auto-closed after sync setup. In the case
1556         // of webstore, it will redirect back to webstore.
1557         RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_);
1558       }
1559
1560       // Observe the sync service if the Webstore tab or the settings tab
1561       // requested a gaia sign in, so that when sign in and sync setup are
1562       // successful, we can redirect to the correct URL, or auto-close the gaia
1563       // sign in tab.
1564       if (original_source == signin::SOURCE_SETTINGS ||
1565           (original_source == signin::SOURCE_WEBSTORE_INSTALL &&
1566            source_ == signin::SOURCE_SETTINGS)) {
1567         ProfileSyncService* sync_service =
1568             ProfileSyncServiceFactory::GetForProfile(profile);
1569         if (sync_service)
1570           sync_service->AddObserver(this);
1571       }
1572       break;
1573     }
1574     case AUTO_ACCEPT_REJECTED_FOR_PROFILE:
1575       AddEmailToOneClickRejectedList(profile, email_);
1576       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_REJECTED);
1577       break;
1578     default:
1579       NOTREACHED() << "Invalid auto_accept=" << auto_accept_;
1580       break;
1581   }
1582
1583   CleanTransientState();
1584 }
1585
1586 // It is guaranteed that this method is called before the object is deleted.
1587 void OneClickSigninHelper::WebContentsDestroyed(
1588     content::WebContents* contents) {
1589   Profile* profile =
1590       Profile::FromBrowserContext(contents->GetBrowserContext());
1591   ProfileSyncService* sync_service =
1592       ProfileSyncServiceFactory::GetForProfile(profile);
1593   if (sync_service)
1594     sync_service->RemoveObserver(this);
1595 }
1596
1597 void OneClickSigninHelper::OnStateChanged() {
1598   // We only add observer for ProfileSyncService when original_continue_url_ is
1599   // not empty.
1600   DCHECK(!original_continue_url_.is_empty());
1601
1602   content::WebContents* contents = web_contents();
1603   Profile* profile =
1604       Profile::FromBrowserContext(contents->GetBrowserContext());
1605   ProfileSyncService* sync_service =
1606       ProfileSyncServiceFactory::GetForProfile(profile);
1607
1608   // At this point, the sign in process is complete, and control has been handed
1609   // back to the sync engine. Close the gaia sign in tab if
1610   // |original_continue_url_| contains the |auto_close| parameter. Otherwise,
1611   // wait for sync setup to complete and then navigate to
1612   // |original_continue_url_|.
1613   if (signin::IsAutoCloseEnabledInURL(original_continue_url_)) {
1614     // Close the gaia sign in tab via a task to make sure we aren't in the
1615     // middle of any webui handler code.
1616     base::MessageLoop::current()->PostTask(
1617         FROM_HERE,
1618         base::Bind(&CloseTab, base::Unretained(contents)));
1619   } else {
1620     // Sync setup not completed yet.
1621     if (sync_service->FirstSetupInProgress())
1622       return;
1623
1624     if (sync_service->sync_initialized() &&
1625         signin::GetSourceForPromoURL(original_continue_url_)
1626             != signin::SOURCE_SETTINGS) {
1627       contents->GetController().LoadURL(original_continue_url_,
1628                                         content::Referrer(),
1629                                         content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1630                                         std::string());
1631     }
1632   }
1633
1634   // Clears |original_continue_url_| here instead of in CleanTransientState,
1635   // because it is used in OnStateChanged which occurs later.
1636   original_continue_url_ = GURL();
1637   sync_service->RemoveObserver(this);
1638 }
1639
1640 OneClickSigninSyncStarter::Callback
1641     OneClickSigninHelper::CreateSyncStarterCallback() {
1642   // The callback will only be invoked if this object is still alive when sync
1643   // setup is completed. This is correct because this object is only deleted
1644   // when the web contents that potentially shows a blank page is deleted.
1645   return base::Bind(&OneClickSigninHelper::SyncSetupCompletedCallback,
1646                     weak_pointer_factory_.GetWeakPtr());
1647 }
1648
1649 void OneClickSigninHelper::SyncSetupCompletedCallback(
1650     OneClickSigninSyncStarter::SyncSetupResult result) {
1651   if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE &&
1652       web_contents()) {
1653     GURL current_url = web_contents()->GetVisibleURL();
1654
1655     // If the web contents is showing a blank page and not about to be closed,
1656     // redirect to the NTP or apps page.
1657     if (signin::IsContinueUrlForWebBasedSigninFlow(current_url) &&
1658         !signin::IsAutoCloseEnabledInURL(original_continue_url_)) {
1659       RedirectToNtpOrAppsPage(
1660           web_contents(),
1661           signin::GetSourceForPromoURL(original_continue_url_));
1662     }
1663   }
1664 }