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