existing_user_controller.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 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 <algorithm> 8#include <functional> 9#include <map> 10 11#include "app/l10n_util.h" 12#include "app/resource_bundle.h" 13#include "base/command_line.h" 14#include "base/message_loop.h" 15#include "base/stl_util-inl.h" 16#include "base/utf_string_conversions.h" 17#include "base/values.h" 18#include "chrome/browser/chromeos/boot_times_loader.h" 19#include "chrome/browser/chromeos/cros/cros_library.h" 20#include "chrome/browser/chromeos/cros/cryptohome_library.h" 21#include "chrome/browser/chromeos/cros/login_library.h" 22#include "chrome/browser/chromeos/cros/network_library.h" 23#include "chrome/browser/chromeos/cros_settings_provider_user.h" 24#include "chrome/browser/chromeos/login/background_view.h" 25#include "chrome/browser/chromeos/login/help_app_launcher.h" 26#include "chrome/browser/chromeos/login/helper.h" 27#include "chrome/browser/chromeos/login/login_utils.h" 28#include "chrome/browser/chromeos/login/message_bubble.h" 29#include "chrome/browser/chromeos/login/wizard_controller.h" 30#include "chrome/browser/chromeos/view_ids.h" 31#include "chrome/browser/chromeos/wm_ipc.h" 32#include "chrome/browser/views/window.h" 33#include "chrome/common/chrome_switches.h" 34#include "chrome/common/net/gaia/google_service_auth_error.h" 35#include "gfx/native_widget_types.h" 36#include "grit/chromium_strings.h" 37#include "grit/generated_resources.h" 38#include "grit/theme_resources.h" 39#include "views/screen.h" 40#include "views/widget/widget_gtk.h" 41#include "views/window/window.h" 42 43namespace chromeos { 44 45namespace { 46 47// Max number of users we'll show. The true max is the min of this and the 48// number of windows that fit on the screen. 49const size_t kMaxUsers = 5; 50 51// Used to indicate no user has been selected. 52const size_t kNotSelected = -1; 53 54// Offset of cursor in first position from edit left side. It's used to position 55// info bubble arrow to cursor. 56const int kCursorOffset = 5; 57 58// Used to handle the asynchronous response of deleting a cryptohome directory. 59class RemoveAttempt : public CryptohomeLibrary::Delegate { 60 public: 61 explicit RemoveAttempt(const std::string& user_email) 62 : user_email_(user_email) { 63 if (CrosLibrary::Get()->EnsureLoaded()) { 64 CrosLibrary::Get()->GetCryptohomeLibrary()->AsyncRemove( 65 user_email_, this); 66 } 67 } 68 69 void OnComplete(bool success, int return_code) { 70 // Log the error, but there's not much we can do. 71 if (!success) { 72 VLOG(1) << "Removal of cryptohome for " << user_email_ 73 << " failed, return code: " << return_code; 74 } 75 delete this; 76 } 77 78 private: 79 std::string user_email_; 80}; 81 82// Checks if display names are unique. If there are duplicates, enables 83// tooltips with full emails to let users distinguish their accounts. 84// Otherwise, disables the tooltips. 85void EnableTooltipsIfNeeded(const std::vector<UserController*>& controllers) { 86 std::map<std::string, int> visible_display_names; 87 for (size_t i = 0; i + 1 < controllers.size(); ++i) { 88 const std::string& display_name = 89 controllers[i]->user().GetDisplayName(); 90 ++visible_display_names[display_name]; 91 } 92 for (size_t i = 0; i < controllers.size(); ++i) { 93 const std::string& display_name = 94 controllers[i]->user().GetDisplayName(); 95 bool show_tooltip = controllers[i]->is_new_user() || 96 controllers[i]->is_guest() || 97 visible_display_names[display_name] > 1; 98 controllers[i]->EnableNameTooltip(show_tooltip); 99 } 100} 101 102} // namespace 103 104ExistingUserController* 105 ExistingUserController::delete_scheduled_instance_ = NULL; 106 107// TODO(xiyuan): Wait for the cached settings update before using them. 108ExistingUserController::ExistingUserController( 109 const std::vector<UserManager::User>& users, 110 const gfx::Rect& background_bounds) 111 : background_bounds_(background_bounds), 112 background_window_(NULL), 113 background_view_(NULL), 114 selected_view_index_(kNotSelected), 115 num_login_attempts_(0), 116 bubble_(NULL), 117 user_settings_(new UserCrosSettingsProvider()) { 118 if (delete_scheduled_instance_) 119 delete_scheduled_instance_->Delete(); 120 121 // Caclulate the max number of users from available screen size. 122 if (UserCrosSettingsProvider::cached_show_users_on_signin()) { 123 size_t max_users = kMaxUsers; 124 int screen_width = background_bounds.width(); 125 if (screen_width > 0) { 126 max_users = std::max(static_cast<size_t>(2), std::min(kMaxUsers, 127 static_cast<size_t>((screen_width - login::kUserImageSize) 128 / (UserController::kUnselectedSize + 129 UserController::kPadding)))); 130 } 131 132 size_t visible_users_count = std::min(users.size(), max_users - 1); 133 for (size_t i = 0; i < users.size(); ++i) { 134 if (controllers_.size() == visible_users_count) 135 break; 136 137 // TODO(xiyuan): Clean user profile whose email is not in whitelist. 138 if (UserCrosSettingsProvider::cached_allow_new_user() || 139 UserCrosSettingsProvider::IsEmailInCachedWhitelist( 140 users[i].email())) { 141 controllers_.push_back(new UserController(this, users[i])); 142 } 143 } 144 } 145 146 if (!controllers_.empty() && UserCrosSettingsProvider::cached_allow_guest()) 147 controllers_.push_back(new UserController(this, true)); 148 149 // Add the view representing the new user. 150 controllers_.push_back(new UserController(this, false)); 151} 152 153void ExistingUserController::Init() { 154 if (!background_window_) { 155 std::string url_string = 156 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 157 switches::kScreenSaverUrl); 158 159 background_window_ = BackgroundView::CreateWindowContainingView( 160 background_bounds_, 161 GURL(url_string), 162 &background_view_); 163 background_view_->EnableShutdownButton(); 164 165 if (!WizardController::IsDeviceRegistered()) { 166 background_view_->SetOobeProgressBarVisible(true); 167 background_view_->SetOobeProgress(chromeos::BackgroundView::SIGNIN); 168 } 169 170 background_window_->Show(); 171 } 172 // If there's only new user pod, show the guest session link on it. 173 bool show_guest_link = controllers_.size() == 1; 174 for (size_t i = 0; i < controllers_.size(); ++i) { 175 (controllers_[i])->Init(static_cast<int>(i), 176 static_cast<int>(controllers_.size()), 177 show_guest_link); 178 } 179 180 EnableTooltipsIfNeeded(controllers_); 181 182 WmMessageListener::instance()->AddObserver(this); 183 184 LoginUtils::Get()->PrewarmAuthentication(); 185 if (CrosLibrary::Get()->EnsureLoaded()) 186 CrosLibrary::Get()->GetLoginLibrary()->EmitLoginPromptReady(); 187} 188 189void ExistingUserController::OwnBackground( 190 views::Widget* background_widget, 191 chromeos::BackgroundView* background_view) { 192 DCHECK(!background_window_); 193 background_window_ = background_widget; 194 background_view_ = background_view; 195} 196 197void ExistingUserController::LoginNewUser(const std::string& username, 198 const std::string& password) { 199 SelectNewUser(); 200 UserController* new_user = controllers_.back(); 201 DCHECK(new_user->is_new_user()); 202 if (!new_user->is_new_user()) 203 return; 204 NewUserView* new_user_view = new_user->new_user_view(); 205 new_user_view->SetUsername(username); 206 207 if (password.empty()) 208 return; 209 210 new_user_view->SetPassword(password); 211 new_user_view->Login(); 212} 213 214void ExistingUserController::SelectNewUser() { 215 SelectUser(controllers_.size() - 1); 216} 217 218ExistingUserController::~ExistingUserController() { 219 ClearErrors(); 220 221 if (background_window_) 222 background_window_->Close(); 223 224 WmMessageListener::instance()->RemoveObserver(this); 225 226 STLDeleteElements(&controllers_); 227} 228 229void ExistingUserController::Delete() { 230 delete_scheduled_instance_ = NULL; 231 delete this; 232} 233 234void ExistingUserController::ProcessWmMessage(const WmIpc::Message& message, 235 GdkWindow* window) { 236 if (message.type() != WM_IPC_MESSAGE_CHROME_CREATE_GUEST_WINDOW) 237 return; 238 239 ActivateWizard(std::string()); 240} 241 242void ExistingUserController::SendSetLoginState(bool is_enabled) { 243 WmIpc::Message message(WM_IPC_MESSAGE_WM_SET_LOGIN_STATE); 244 message.set_param(0, is_enabled); 245 WmIpc::instance()->SendMessage(message); 246} 247 248void ExistingUserController::Login(UserController* source, 249 const string16& password) { 250 BootTimesLoader::Get()->RecordLoginAttempted(); 251 std::vector<UserController*>::const_iterator i = 252 std::find(controllers_.begin(), controllers_.end(), source); 253 DCHECK(i != controllers_.end()); 254 255 if (i == controllers_.begin() + selected_view_index_) { 256 num_login_attempts_++; 257 } else { 258 selected_view_index_ = i - controllers_.begin(); 259 num_login_attempts_ = 0; 260 } 261 262 // Disable clicking on other windows. 263 SendSetLoginState(false); 264 265 // Use the same LoginPerformer for subsequent login as it has state 266 // such as CAPTCHA challenge token & corresponding user input. 267 if (!login_performer_.get() || num_login_attempts_ <= 1) { 268 login_performer_.reset(new LoginPerformer(this)); 269 } 270 login_performer_->Login(controllers_[selected_view_index_]->user().email(), 271 UTF16ToUTF8(password)); 272} 273 274void ExistingUserController::WhiteListCheckFailed(const std::string& email) { 275 ShowError(IDS_LOGIN_ERROR_WHITELIST, email); 276 277 // Reenable userview and use ClearAndEnablePassword to keep username on 278 // screen with the error bubble. 279 controllers_[selected_view_index_]->ClearAndEnablePassword(); 280 281 // Reenable clicking on other windows. 282 SendSetLoginState(true); 283} 284 285void ExistingUserController::LoginOffTheRecord() { 286 // Check allow_guest in case this call is fired from key accelerator. 287 if (!UserCrosSettingsProvider::cached_allow_guest()) 288 return; 289 290 // Disable clicking on other windows. 291 SendSetLoginState(false); 292 293 login_performer_.reset(new LoginPerformer(this)); 294 login_performer_->LoginOffTheRecord(); 295} 296 297void ExistingUserController::ClearErrors() { 298 // bubble_ will be set to NULL in callback. 299 if (bubble_) 300 bubble_->Close(); 301} 302 303void ExistingUserController::OnUserSelected(UserController* source) { 304 std::vector<UserController*>::const_iterator i = 305 std::find(controllers_.begin(), controllers_.end(), source); 306 DCHECK(i != controllers_.end()); 307 size_t new_selected_index = i - controllers_.begin(); 308 if (new_selected_index != selected_view_index_ && 309 selected_view_index_ != kNotSelected) { 310 controllers_[selected_view_index_]->ClearAndEnableFields(); 311 controllers_[new_selected_index]->ClearAndEnableFields(); 312 login_performer_.reset(NULL); 313 num_login_attempts_ = 0; 314 } 315 selected_view_index_ = new_selected_index; 316} 317 318void ExistingUserController::ActivateWizard(const std::string& screen_name) { 319 // WizardController takes care of deleting itself when done. 320 WizardController* controller = new WizardController(); 321 322 // Give the background window to the controller. 323 controller->OwnBackground(background_window_, background_view_); 324 background_window_ = NULL; 325 326 controller->Init(screen_name, background_bounds_); 327 controller->set_start_url(start_url_); 328 controller->Show(); 329 330 // And schedule us for deletion. We delay for a second as the window manager 331 // is doing an animation with our windows. 332 DCHECK(!delete_scheduled_instance_); 333 delete_scheduled_instance_ = this; 334 delete_timer_.Start(base::TimeDelta::FromSeconds(1), this, 335 &ExistingUserController::Delete); 336} 337 338void ExistingUserController::RemoveUser(UserController* source) { 339 ClearErrors(); 340 341 // TODO(xiyuan): Wait for the cached settings update before using them. 342 if (UserCrosSettingsProvider::cached_owner() == source->user().email()) { 343 // Owner is not allowed to be removed from the device. 344 return; 345 } 346 347 UserManager::Get()->RemoveUser(source->user().email()); 348 349 controllers_.erase(controllers_.begin() + source->user_index()); 350 351 EnableTooltipsIfNeeded(controllers_); 352 353 // Update user count before unmapping windows, otherwise window manager won't 354 // be in the right state. 355 int new_size = static_cast<int>(controllers_.size()); 356 for (int i = 0; i < new_size; ++i) 357 controllers_[i]->UpdateUserCount(i, new_size); 358 359 // Delete the encrypted user directory. 360 new RemoveAttempt(source->user().email()); 361 // We need to unmap entry windows, the windows will be unmapped in destructor. 362 delete source; 363} 364 365void ExistingUserController::SelectUser(int index) { 366 if (index >= 0 && index < static_cast<int>(controllers_.size()) && 367 index != static_cast<int>(selected_view_index_)) { 368 WmIpc::Message message(WM_IPC_MESSAGE_WM_SELECT_LOGIN_USER); 369 message.set_param(0, index); 370 WmIpc::instance()->SendMessage(message); 371 } 372} 373 374void ExistingUserController::OnLoginFailure(const LoginFailure& failure) { 375 std::string error = failure.GetErrorString(); 376 377 // Check networking after trying to login in case user is 378 // cached locally or the local admin account. 379 NetworkLibrary* network = CrosLibrary::Get()->GetNetworkLibrary(); 380 if (!network || !CrosLibrary::Get()->EnsureLoaded()) { 381 ShowError(IDS_LOGIN_ERROR_NO_NETWORK_LIBRARY, error); 382 } else if (!network->Connected()) { 383 ShowError(IDS_LOGIN_ERROR_OFFLINE_FAILED_NETWORK_NOT_CONNECTED, error); 384 } else { 385 if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED && 386 failure.error().state() == GoogleServiceAuthError::CAPTCHA_REQUIRED) { 387 if (!failure.error().captcha().image_url.is_empty()) { 388 CaptchaView* view = 389 new CaptchaView(failure.error().captcha().image_url); 390 view->set_delegate(this); 391 views::Window* window = browser::CreateViewsWindow( 392 GetNativeWindow(), gfx::Rect(), view); 393 window->SetIsAlwaysOnTop(true); 394 window->Show(); 395 } else { 396 LOG(WARNING) << "No captcha image url was found?"; 397 ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error); 398 } 399 } else if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED && 400 failure.error().state() == 401 GoogleServiceAuthError::HOSTED_NOT_ALLOWED) { 402 ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED, error); 403 } else { 404 if (controllers_[selected_view_index_]->is_new_user()) 405 ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_NEW, error); 406 else 407 ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error); 408 } 409 } 410 411 controllers_[selected_view_index_]->ClearAndEnablePassword(); 412 413 // Reenable clicking on other windows. 414 SendSetLoginState(true); 415} 416 417void ExistingUserController::AppendStartUrlToCmdline() { 418 if (start_url_.is_valid()) 419 CommandLine::ForCurrentProcess()->AppendArg(start_url_.spec()); 420} 421 422gfx::NativeWindow ExistingUserController::GetNativeWindow() const { 423 return GTK_WINDOW( 424 static_cast<views::WidgetGtk*>(background_window_)->GetNativeView()); 425} 426 427void ExistingUserController::ShowError(int error_id, 428 const std::string& details) { 429 ClearErrors(); 430 std::wstring error_text; 431 // GetStringF fails on debug build if there's no replacement in the string. 432 if (error_id == IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED) { 433 error_text = l10n_util::GetStringF( 434 error_id, l10n_util::GetString(IDS_PRODUCT_OS_NAME)); 435 } else { 436 error_text = l10n_util::GetString(error_id); 437 } 438 // TODO(dpolukhin): show detailed error info. |details| string contains 439 // low level error info that is not localized and even is not user friendly. 440 // For now just ignore it because error_text contains all required information 441 // for end users, developers can see details string in Chrome logs. 442 443 gfx::Rect bounds = controllers_[selected_view_index_]->GetScreenBounds(); 444 BubbleBorder::ArrowLocation arrow; 445 if (controllers_[selected_view_index_]->is_new_user()) { 446 arrow = BubbleBorder::LEFT_TOP; 447 } else { 448 // Point info bubble arrow to cursor position (approximately). 449 bounds.set_width(kCursorOffset * 2); 450 arrow = BubbleBorder::BOTTOM_LEFT; 451 } 452 std::wstring help_link; 453 if (error_id == IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED) { 454 help_link = l10n_util::GetString(IDS_LEARN_MORE); 455 } else if (num_login_attempts_ > static_cast<size_t>(1)) { 456 help_link = l10n_util::GetString(IDS_CANT_ACCESS_ACCOUNT_BUTTON); 457 } 458 459 bubble_ = MessageBubble::Show( 460 controllers_[selected_view_index_]->controls_window(), 461 bounds, 462 arrow, 463 ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING), 464 error_text, 465 help_link, 466 this); 467} 468 469void ExistingUserController::OnLoginSuccess( 470 const std::string& username, 471 const std::string& password, 472 const GaiaAuthConsumer::ClientLoginResult& credentials, 473 bool pending_requests) { 474 // LoginPerformer instance will delete itself once online auth result is OK. 475 // In case of failure it'll bring up ScreenLock and ask for 476 // correct password/display error message. 477 // Even in case when following online,offline protocol and returning 478 // requests_pending = false, let LoginPerformer delete itself. 479 login_performer_->set_delegate(NULL); 480 LoginPerformer* performer = login_performer_.release(); 481 performer = NULL; 482 AppendStartUrlToCmdline(); 483 if (selected_view_index_ + 1 == controllers_.size() && 484 !UserManager::Get()->IsKnownUser(username)) { 485 // For new user login don't launch browser until we pass image screen. 486 LoginUtils::Get()->EnableBrowserLaunch(false); 487 LoginUtils::Get()->CompleteLogin(username, password, credentials); 488 ActivateWizard(WizardController::IsDeviceRegistered() ? 489 WizardController::kUserImageScreenName : 490 WizardController::kRegistrationScreenName); 491 } else { 492 // Hide the login windows now. 493 WmIpc::Message message(WM_IPC_MESSAGE_WM_HIDE_LOGIN); 494 WmIpc::instance()->SendMessage(message); 495 496 LoginUtils::Get()->CompleteLogin(username, password, credentials); 497 498 // Delay deletion as we're on the stack. 499 MessageLoop::current()->DeleteSoon(FROM_HERE, this); 500 } 501} 502 503void ExistingUserController::OnOffTheRecordLoginSuccess() { 504 if (WizardController::IsDeviceRegistered()) { 505 LoginUtils::Get()->CompleteOffTheRecordLogin(start_url_); 506 } else { 507 // Postpone CompleteOffTheRecordLogin until registration completion. 508 ActivateWizard(WizardController::kRegistrationScreenName); 509 } 510} 511 512void ExistingUserController::OnPasswordChangeDetected( 513 const GaiaAuthConsumer::ClientLoginResult& credentials) { 514 // When signing in as a "New user" always remove old cryptohome. 515 if (selected_view_index_ == controllers_.size() - 1) { 516 login_performer_->ResyncEncryptedData(); 517 return; 518 } 519 520 PasswordChangedView* view = new PasswordChangedView(this); 521 views::Window* window = browser::CreateViewsWindow(GetNativeWindow(), 522 gfx::Rect(), 523 view); 524 window->SetIsAlwaysOnTop(true); 525 window->Show(); 526} 527 528void ExistingUserController::OnHelpLinkActivated() { 529 DCHECK(login_performer_->error().state() != GoogleServiceAuthError::NONE); 530 if (!help_app_.get()) 531 help_app_.reset(new HelpAppLauncher(GetNativeWindow())); 532 switch (login_performer_->error().state()) { 533 case GoogleServiceAuthError::CONNECTION_FAILED: 534 help_app_->ShowHelpTopic( 535 HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE); 536 break; 537 case GoogleServiceAuthError::ACCOUNT_DISABLED: 538 help_app_->ShowHelpTopic( 539 HelpAppLauncher::HELP_ACCOUNT_DISABLED); 540 break; 541 case GoogleServiceAuthError::HOSTED_NOT_ALLOWED: 542 help_app_->ShowHelpTopic( 543 HelpAppLauncher::HELP_HOSTED_ACCOUNT); 544 break; 545 default: 546 help_app_->ShowHelpTopic(login_performer_->login_timed_out() ? 547 HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE : 548 HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT); 549 break; 550 } 551} 552 553void ExistingUserController::OnCaptchaEntered(const std::string& captcha) { 554 login_performer_->set_captcha(captcha); 555} 556 557void ExistingUserController::RecoverEncryptedData( 558 const std::string& old_password) { 559 login_performer_->RecoverEncryptedData(old_password); 560} 561 562void ExistingUserController::ResyncEncryptedData() { 563 login_performer_->ResyncEncryptedData(); 564} 565 566} // namespace chromeos 567