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