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