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