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