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