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