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