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