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