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
94namespace {
95
96// ConfirmEmailDialogDelegate -------------------------------------------------
97
98class 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
144void 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
154ConfirmEmailDialogDelegate::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
166ConfirmEmailDialogDelegate::~ConfirmEmailDialogDelegate() {
167}
168
169base::string16 ConfirmEmailDialogDelegate::GetTitle() {
170  return l10n_util::GetStringUTF16(
171      IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_TITLE);
172}
173
174base::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
180base::string16 ConfirmEmailDialogDelegate::GetAcceptButtonTitle() {
181  return l10n_util::GetStringUTF16(
182      IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_OK_BUTTON);
183}
184
185base::string16 ConfirmEmailDialogDelegate::GetCancelButtonTitle() {
186  return l10n_util::GetStringUTF16(
187      IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON);
188}
189
190base::string16 ConfirmEmailDialogDelegate::GetLinkText() const {
191  return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
192}
193
194void ConfirmEmailDialogDelegate::OnAccepted() {
195  base::ResetAndReturn(&callback_).Run(CREATE_NEW_USER);
196}
197
198void ConfirmEmailDialogDelegate::OnCanceled() {
199  base::ResetAndReturn(&callback_).Run(START_SYNC);
200}
201
202void ConfirmEmailDialogDelegate::OnClosed() {
203  base::ResetAndReturn(&callback_).Run(CLOSE);
204}
205
206void 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.
225void 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
232void 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
239void 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.
251void 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
285void 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
323void 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".
333signin::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.
377bool 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.
397bool 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.
413std::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.
429class 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
448CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents)
449    : WebContentsObserver(contents) {
450  history_index_to_remove_ =
451      web_contents()->GetController().GetLastCommittedEntryIndex();
452}
453
454CurrentHistoryCleaner::~CurrentHistoryCleaner() {
455}
456
457void 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
482void CurrentHistoryCleaner::WebContentsDestroyed() {
483  delete this;  // Failure.
484}
485
486}  // namespace
487
488
489// StartSyncArgs --------------------------------------------------------------
490
491OneClickSigninHelper::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
499OneClickSigninHelper::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
532OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {}
533
534// SyncStarterWrapper ---------------------------------------------------------
535
536OneClickSigninHelper::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
548OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() {
549  BrowserList::RemoveObserver(this);
550}
551
552void 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
565void
566OneClickSigninHelper::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
581void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure(
582  const GoogleServiceAuthError& error) {
583  DisplayErrorBubble(error.ToString());
584  base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
585}
586
587void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved(
588    Browser* browser) {
589  if (args_.browser == browser)
590    args_.browser = NULL;
591}
592
593void 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
607void 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
636void 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
644void 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
653void
654OneClickSigninHelper::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
670DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper);
671
672// static
673const int OneClickSigninHelper::kMaxNavigationsSince = 10;
674
675OneClickSigninHelper::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
696OneClickSigninHelper::~OneClickSigninHelper() {}
697
698// static
699void 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
762void 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
772bool 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
875OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread(
876    net::URLRequest* request,
877    ProfileIOData* io_data) {
878  return CanOfferOnIOThreadImpl(request->url(), request, io_data);
879}
880
881// static
882OneClickSigninHelper::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
941void 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
1038void OneClickSigninHelper::LogConfirmHistogramValue(int action) {
1039  UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action,
1040                            one_click_signin::HISTOGRAM_CONFIRM_MAX);
1041}
1042// static
1043void 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
1123void 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
1133bool 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
1180void 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
1201void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
1202    content::WebContents* contents, signin::Source source) {
1203  if (source != signin::SOURCE_SETTINGS) {
1204    RedirectToNtpOrAppsPage(contents, source);
1205  }
1206}
1207
1208void 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
1224void 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
1248void 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
1257void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() {
1258  do_not_clear_pending_email_ = true;
1259}
1260
1261void OneClickSigninHelper::set_do_not_start_sync_for_testing() {
1262  do_not_start_sync_for_testing_ = true;
1263}
1264
1265void 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
1287void 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
1311void 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
1566OneClickSigninSyncStarter::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
1575void 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