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