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