existing_user_controller.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2011 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/chromeos/login/existing_user_controller.h" 6 7#include "base/command_line.h" 8#include "base/message_loop.h" 9#include "base/utf_string_conversions.h" 10#include "base/values.h" 11#include "chrome/browser/browser_process.h" 12#include "chrome/browser/chromeos/boot_times_loader.h" 13#include "chrome/browser/chromeos/cros/cros_library.h" 14#include "chrome/browser/chromeos/cros/cryptohome_library.h" 15#include "chrome/browser/chromeos/cros/login_library.h" 16#include "chrome/browser/chromeos/cros/network_library.h" 17#include "chrome/browser/chromeos/login/background_view.h" 18#include "chrome/browser/chromeos/login/helper.h" 19#include "chrome/browser/chromeos/login/login_utils.h" 20#include "chrome/browser/chromeos/login/views_login_display.h" 21#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h" 22#include "chrome/browser/chromeos/login/wizard_controller.h" 23#include "chrome/browser/chromeos/status/status_area_view.h" 24#include "chrome/browser/chromeos/user_cros_settings_provider.h" 25#include "chrome/browser/google/google_util.h" 26#include "chrome/browser/prefs/pref_service.h" 27#include "chrome/browser/profiles/profile_manager.h" 28#include "chrome/browser/ui/views/window.h" 29#include "chrome/common/chrome_switches.h" 30#include "chrome/common/net/gaia/google_service_auth_error.h" 31#include "chrome/common/notification_service.h" 32#include "chrome/common/notification_type.h" 33#include "chrome/common/pref_names.h" 34#include "grit/generated_resources.h" 35#include "ui/base/l10n/l10n_util.h" 36#include "views/window/window.h" 37 38namespace chromeos { 39 40namespace { 41 42// Url for setting up sync authentication. 43const char kSettingsSyncLoginURL[] = "chrome://settings/personal"; 44 45// URL that will be opened on when user logs in first time on the device. 46const char kGetStartedURL[] = 47 "chrome-extension://cbmhffdpiobpchciemffincgahkkljig/index.html"; 48 49// URL for account creation. 50const char kCreateAccountURL[] = 51 "https://www.google.com/accounts/NewAccount?service=mail"; 52 53// Landing URL when launching Guest mode to fix captive portal. 54const char kCaptivePortalLaunchURL[] = "http://www.google.com/"; 55 56// Used to handle the asynchronous response of deleting a cryptohome directory. 57class RemoveAttempt : public CryptohomeLibrary::Delegate { 58 public: 59 explicit RemoveAttempt(const std::string& user_email) 60 : user_email_(user_email) { 61 if (CrosLibrary::Get()->EnsureLoaded()) { 62 CrosLibrary::Get()->GetCryptohomeLibrary()->AsyncRemove( 63 user_email_, this); 64 } 65 } 66 67 void OnComplete(bool success, int return_code) { 68 // Log the error, but there's not much we can do. 69 if (!success) { 70 VLOG(1) << "Removal of cryptohome for " << user_email_ 71 << " failed, return code: " << return_code; 72 } 73 delete this; 74 } 75 76 private: 77 std::string user_email_; 78}; 79 80} // namespace 81 82// static 83ExistingUserController* 84 ExistingUserController::current_controller_ = NULL; 85 86//////////////////////////////////////////////////////////////////////////////// 87// ExistingUserController, public: 88 89ExistingUserController::ExistingUserController( 90 const gfx::Rect& background_bounds) 91 : background_bounds_(background_bounds), 92 background_window_(NULL), 93 background_view_(NULL), 94 num_login_attempts_(0), 95 user_settings_(new UserCrosSettingsProvider), 96 method_factory_(this) { 97 if (current_controller_) 98 current_controller_->Delete(); 99 current_controller_ = this; 100 101 login_display_.reset(CreateLoginDisplay(this, background_bounds)); 102 103 registrar_.Add(this, 104 NotificationType::LOGIN_USER_IMAGE_CHANGED, 105 NotificationService::AllSources()); 106} 107 108void ExistingUserController::Init(const UserVector& users) { 109 if (g_browser_process) { 110 PrefService* state = g_browser_process->local_state(); 111 if (state) { 112 std::string owner_locale = state->GetString(prefs::kOwnerLocale); 113 // Ensure that we start with owner's locale. 114 if (!owner_locale.empty() && 115 state->GetString(prefs::kApplicationLocale) != owner_locale && 116 !state->IsManagedPreference(prefs::kApplicationLocale)) { 117 state->SetString(prefs::kApplicationLocale, owner_locale); 118 state->ScheduleSavePersistentPrefs(); 119 LanguageSwitchMenu::SwitchLanguage(owner_locale); 120 } 121 } 122 } 123 if (!background_window_) { 124 background_window_ = BackgroundView::CreateWindowContainingView( 125 background_bounds_, 126 GURL(), 127 &background_view_); 128 background_view_->EnableShutdownButton(true); 129 130 if (!WizardController::IsDeviceRegistered()) { 131 background_view_->SetOobeProgressBarVisible(true); 132 background_view_->SetOobeProgress(chromeos::BackgroundView::SIGNIN); 133 } 134 135 background_window_->Show(); 136 } 137 138 UserVector filtered_users; 139 if (UserCrosSettingsProvider::cached_show_users_on_signin()) { 140 for (size_t i = 0; i < users.size(); ++i) 141 // TODO(xiyuan): Clean user profile whose email is not in whitelist. 142 if (UserCrosSettingsProvider::cached_allow_new_user() || 143 UserCrosSettingsProvider::IsEmailInCachedWhitelist( 144 users[i].email())) { 145 filtered_users.push_back(users[i]); 146 } 147 } 148 149 // If no user pods are visible, fallback to single new user pod which will 150 // have guest session link. 151 bool show_guest = UserCrosSettingsProvider::cached_allow_guest() && 152 !filtered_users.empty(); 153 bool show_new_user = true; 154 login_display_->set_parent_window(GetNativeWindow()); 155 login_display_->Init(filtered_users, show_guest, show_new_user); 156 157 LoginUtils::Get()->PrewarmAuthentication(); 158 if (CrosLibrary::Get()->EnsureLoaded()) 159 CrosLibrary::Get()->GetLoginLibrary()->EmitLoginPromptReady(); 160} 161 162void ExistingUserController::OwnBackground( 163 views::Widget* background_widget, 164 chromeos::BackgroundView* background_view) { 165 DCHECK(!background_window_); 166 background_window_ = background_widget; 167 background_view_ = background_view; 168} 169 170//////////////////////////////////////////////////////////////////////////////// 171// ExistingUserController, NotificationObserver implementation: 172// 173 174void ExistingUserController::Observe(NotificationType type, 175 const NotificationSource& source, 176 const NotificationDetails& details) { 177 if (type != NotificationType::LOGIN_USER_IMAGE_CHANGED) 178 return; 179 180 UserManager::User* user = Details<UserManager::User>(details).ptr(); 181 login_display_->OnUserImageChanged(user); 182} 183 184//////////////////////////////////////////////////////////////////////////////// 185// ExistingUserController, private: 186 187ExistingUserController::~ExistingUserController() { 188 if (background_window_) 189 background_window_->Close(); 190 191 DCHECK(current_controller_ != NULL); 192 current_controller_ = NULL; 193} 194 195//////////////////////////////////////////////////////////////////////////////// 196// ExistingUserController, LoginDisplay::Delegate implementation: 197// 198 199void ExistingUserController::CreateAccount() { 200 guest_mode_url_ = 201 google_util::AppendGoogleLocaleParam(GURL(kCreateAccountURL)); 202 LoginAsGuest(); 203} 204 205void ExistingUserController::FixCaptivePortal() { 206 guest_mode_url_ = GURL(kCaptivePortalLaunchURL); 207 LoginAsGuest(); 208} 209 210void ExistingUserController::Login(const std::string& username, 211 const std::string& password) { 212 if (username.empty() || password.empty()) 213 return; 214 SetStatusAreaEnabled(false); 215 // Disable clicking on other windows. 216 login_display_->SetUIEnabled(false); 217 218 BootTimesLoader::Get()->RecordLoginAttempted(); 219 220 if (last_login_attempt_username_ != username) { 221 last_login_attempt_username_ = username; 222 num_login_attempts_ = 0; 223 } 224 num_login_attempts_++; 225 226 // Use the same LoginPerformer for subsequent login as it has state 227 // such as CAPTCHA challenge token & corresponding user input. 228 if (!login_performer_.get() || num_login_attempts_ <= 1) { 229 LoginPerformer::Delegate* delegate = this; 230 if (login_performer_delegate_.get()) 231 delegate = login_performer_delegate_.get(); 232 // Only one instance of LoginPerformer should exist at a time. 233 login_performer_.reset(NULL); 234 login_performer_.reset(new LoginPerformer(delegate)); 235 } 236 login_performer_->Login(username, password); 237 WizardAccessibilityHelper::GetInstance()->MaybeSpeak( 238 l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNING_IN).c_str(), 239 false, true); 240} 241 242void ExistingUserController::LoginAsGuest() { 243 SetStatusAreaEnabled(false); 244 // Disable clicking on other windows. 245 login_display_->SetUIEnabled(false); 246 247 // Check allow_guest in case this call is fired from key accelerator. 248 // Must not proceed without signature verification. 249 bool trusted_setting_available = user_settings_->RequestTrustedAllowGuest( 250 method_factory_.NewRunnableMethod( 251 &ExistingUserController::LoginAsGuest)); 252 if (!trusted_setting_available) { 253 // Value of AllowGuest setting is still not verified. 254 // Another attempt will be invoked again after verification completion. 255 return; 256 } 257 if (!UserCrosSettingsProvider::cached_allow_guest()) { 258 // Disallowed. 259 return; 260 } 261 262 // Only one instance of LoginPerformer should exist at a time. 263 login_performer_.reset(NULL); 264 login_performer_.reset(new LoginPerformer(this)); 265 login_performer_->LoginOffTheRecord(); 266 WizardAccessibilityHelper::GetInstance()->MaybeSpeak( 267 l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNIN_OFFRECORD).c_str(), 268 false, true); 269} 270 271void ExistingUserController::OnUserSelected(const std::string& username) { 272 login_performer_.reset(NULL); 273 num_login_attempts_ = 0; 274} 275 276void ExistingUserController::RemoveUser(const std::string& username) { 277 // Owner is not allowed to be removed from the device. 278 // Must not proceed without signature verification. 279 UserCrosSettingsProvider user_settings; 280 bool trusted_owner_available = user_settings.RequestTrustedOwner( 281 method_factory_.NewRunnableMethod(&ExistingUserController::RemoveUser, 282 username)); 283 if (!trusted_owner_available) { 284 // Value of owner email is still not verified. 285 // Another attempt will be invoked after verification completion. 286 return; 287 } 288 if (username == UserCrosSettingsProvider::cached_owner()) { 289 // Owner is not allowed to be removed from the device. 290 return; 291 } 292 293 login_display_->OnBeforeUserRemoved(username); 294 295 // Delete user from user list. 296 UserManager::Get()->RemoveUser(username); 297 298 // Delete the encrypted user directory. 299 new RemoveAttempt(username); 300 301 login_display_->OnUserRemoved(username); 302} 303 304//////////////////////////////////////////////////////////////////////////////// 305// ExistingUserController, LoginPerformer::Delegate implementation: 306// 307 308void ExistingUserController::OnLoginFailure(const LoginFailure& failure) { 309 guest_mode_url_ = GURL::EmptyGURL(); 310 std::string error = failure.GetErrorString(); 311 312 // Check networking after trying to login in case user is 313 // cached locally or the local admin account. 314 NetworkLibrary* network = CrosLibrary::Get()->GetNetworkLibrary(); 315 if (!network || !CrosLibrary::Get()->EnsureLoaded()) { 316 ShowError(IDS_LOGIN_ERROR_NO_NETWORK_LIBRARY, error); 317 } else if (!network->Connected()) { 318 ShowError(IDS_LOGIN_ERROR_OFFLINE_FAILED_NETWORK_NOT_CONNECTED, error); 319 } else { 320 if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED && 321 failure.error().state() == GoogleServiceAuthError::CAPTCHA_REQUIRED) { 322 if (!failure.error().captcha().image_url.is_empty()) { 323 CaptchaView* view = 324 new CaptchaView(failure.error().captcha().image_url, false); 325 view->Init(); 326 view->set_delegate(this); 327 views::Window* window = browser::CreateViewsWindow( 328 GetNativeWindow(), gfx::Rect(), view); 329 window->SetIsAlwaysOnTop(true); 330 window->Show(); 331 } else { 332 LOG(WARNING) << "No captcha image url was found?"; 333 ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error); 334 } 335 } else if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED && 336 failure.error().state() == 337 GoogleServiceAuthError::HOSTED_NOT_ALLOWED) { 338 ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED, error); 339 } else if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED && 340 failure.error().state() == 341 GoogleServiceAuthError::SERVICE_UNAVAILABLE) { 342 // SERVICE_UNAVAILABLE is generated in 2 cases: 343 // 1. ClientLogin returns ServiceUnavailable code. 344 // 2. Internet connectivity may be behind the captive portal. 345 // Suggesting user to try sign in to a portal in Guest mode. 346 ShowError(IDS_LOGIN_ERROR_CAPTIVE_PORTAL, error); 347 } else { 348 if (!UserManager::Get()->IsKnownUser(last_login_attempt_username_)) 349 ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_NEW, error); 350 else 351 ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error); 352 } 353 } 354 355 // Reenable clicking on other windows and status area. 356 login_display_->SetUIEnabled(true); 357 SetStatusAreaEnabled(true); 358} 359 360void ExistingUserController::OnLoginSuccess( 361 const std::string& username, 362 const std::string& password, 363 const GaiaAuthConsumer::ClientLoginResult& credentials, 364 bool pending_requests) { 365 // LoginPerformer instance will delete itself once online auth result is OK. 366 // In case of failure it'll bring up ScreenLock and ask for 367 // correct password/display error message. 368 // Even in case when following online,offline protocol and returning 369 // requests_pending = false, let LoginPerformer delete itself. 370 login_performer_->set_delegate(NULL); 371 LoginPerformer* performer = login_performer_.release(); 372 performer = NULL; 373 bool known_user = UserManager::Get()->IsKnownUser(username); 374 bool login_only = 375 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 376 switches::kLoginScreen) == WizardController::kLoginScreenName; 377 // TODO(nkostylev): May add login UI implementation callback call. 378 if (!known_user && !login_only) { 379#if defined(OFFICIAL_BUILD) 380 CommandLine::ForCurrentProcess()->AppendArg(kGetStartedURL); 381#endif // OFFICIAL_BUILD 382 383 if (credentials.two_factor) { 384 // If we have a two factor error and and this is a new user, 385 // load the personal settings page. 386 // TODO(stevenjb): direct the user to a lightweight sync login page. 387 CommandLine::ForCurrentProcess()->AppendArg(kSettingsSyncLoginURL); 388 } 389 390 // For new user login don't launch browser until we pass image screen. 391 LoginUtils::Get()->EnableBrowserLaunch(false); 392 LoginUtils::Get()->CompleteLogin(username, 393 password, 394 credentials, 395 pending_requests); 396 397 ActivateWizard(WizardController::IsDeviceRegistered() ? 398 WizardController::kUserImageScreenName : 399 WizardController::kRegistrationScreenName); 400 } else { 401 LoginUtils::Get()->CompleteLogin(username, 402 password, 403 credentials, 404 pending_requests); 405 406 // Delay deletion as we're on the stack. 407 MessageLoop::current()->DeleteSoon(FROM_HERE, this); 408 } 409} 410 411void ExistingUserController::OnOffTheRecordLoginSuccess() { 412 if (WizardController::IsDeviceRegistered()) { 413 LoginUtils::Get()->CompleteOffTheRecordLogin(guest_mode_url_); 414 } else { 415 // Postpone CompleteOffTheRecordLogin until registration completion. 416 ActivateWizard(WizardController::kRegistrationScreenName); 417 } 418} 419 420void ExistingUserController::OnPasswordChangeDetected( 421 const GaiaAuthConsumer::ClientLoginResult& credentials) { 422 // Must not proceed without signature verification. 423 bool trusted_setting_available = user_settings_->RequestTrustedOwner( 424 method_factory_.NewRunnableMethod( 425 &ExistingUserController::OnPasswordChangeDetected, 426 credentials)); 427 if (!trusted_setting_available) { 428 // Value of owner email is still not verified. 429 // Another attempt will be invoked after verification completion. 430 return; 431 } 432 // TODO(altimofeev): remove this constrain when full sync for the owner will 433 // be correctly handled. 434 bool full_sync_disabled = (UserCrosSettingsProvider::cached_owner() == 435 last_login_attempt_username_); 436 437 PasswordChangedView* view = new PasswordChangedView(this, full_sync_disabled); 438 views::Window* window = browser::CreateViewsWindow(GetNativeWindow(), 439 gfx::Rect(), 440 view); 441 window->SetIsAlwaysOnTop(true); 442 window->Show(); 443} 444 445void ExistingUserController::WhiteListCheckFailed(const std::string& email) { 446 ShowError(IDS_LOGIN_ERROR_WHITELIST, email); 447 448 // Reenable clicking on other windows and status area. 449 login_display_->SetUIEnabled(true); 450 SetStatusAreaEnabled(true); 451} 452 453//////////////////////////////////////////////////////////////////////////////// 454// ExistingUserController, CaptchaView::Delegate implementation: 455// 456 457void ExistingUserController::OnCaptchaEntered(const std::string& captcha) { 458 login_performer_->set_captcha(captcha); 459} 460 461//////////////////////////////////////////////////////////////////////////////// 462// ExistingUserController, PasswordChangedView::Delegate implementation: 463// 464 465void ExistingUserController::RecoverEncryptedData( 466 const std::string& old_password) { 467 // LoginPerformer instance has state of the user so it should exist. 468 if (login_performer_.get()) 469 login_performer_->RecoverEncryptedData(old_password); 470} 471 472void ExistingUserController::ResyncEncryptedData() { 473 // LoginPerformer instance has state of the user so it should exist. 474 if (login_performer_.get()) 475 login_performer_->ResyncEncryptedData(); 476} 477 478//////////////////////////////////////////////////////////////////////////////// 479// ExistingUserController, private: 480 481void ExistingUserController::ActivateWizard(const std::string& screen_name) { 482 // WizardController takes care of deleting itself when done. 483 WizardController* controller = new WizardController(); 484 485 // Give the background window to the controller. 486 controller->OwnBackground(background_window_, background_view_); 487 background_window_ = NULL; 488 489 controller->Init(screen_name, background_bounds_); 490 if (chromeos::UserManager::Get()->IsLoggedInAsGuest()) 491 controller->set_start_url(guest_mode_url_); 492 493 login_display_->OnFadeOut(); 494 495 delete_timer_.Start(base::TimeDelta::FromSeconds(1), this, 496 &ExistingUserController::Delete); 497} 498 499LoginDisplay* ExistingUserController::CreateLoginDisplay( 500 LoginDisplay::Delegate* delegate, const gfx::Rect& background_bounds) { 501 // TODO(rharrison): Create Web UI implementation too. http://crosbug.com/6398. 502 return new ViewsLoginDisplay(delegate, background_bounds); 503} 504 505void ExistingUserController::Delete() { 506 delete this; 507} 508 509gfx::NativeWindow ExistingUserController::GetNativeWindow() const { 510 return background_view_->GetNativeWindow(); 511} 512 513void ExistingUserController::SetStatusAreaEnabled(bool enable) { 514 if (background_view_) { 515 background_view_->SetStatusAreaEnabled(enable); 516 } 517} 518 519void ExistingUserController::ShowError(int error_id, 520 const std::string& details) { 521 // TODO(dpolukhin): show detailed error info. |details| string contains 522 // low level error info that is not localized and even is not user friendly. 523 // For now just ignore it because error_text contains all required information 524 // for end users, developers can see details string in Chrome logs. 525 VLOG(1) << details; 526 HelpAppLauncher::HelpTopic help_topic_id; 527 switch (login_performer_->error().state()) { 528 case GoogleServiceAuthError::CONNECTION_FAILED: 529 help_topic_id = HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE; 530 break; 531 case GoogleServiceAuthError::ACCOUNT_DISABLED: 532 help_topic_id = HelpAppLauncher::HELP_ACCOUNT_DISABLED; 533 break; 534 case GoogleServiceAuthError::HOSTED_NOT_ALLOWED: 535 help_topic_id = HelpAppLauncher::HELP_HOSTED_ACCOUNT; 536 break; 537 default: 538 help_topic_id = login_performer_->login_timed_out() ? 539 HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE : 540 HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT; 541 break; 542 } 543 544 login_display_->ShowError(error_id, num_login_attempts_, help_topic_id); 545} 546 547} // namespace chromeos 548