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