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