// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/login/user_manager.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/path_service.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "crypto/nss_util.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/cros/cryptohome_library.h" #include "chrome/browser/chromeos/cros/input_method_library.h" #include "chrome/browser/chromeos/login/default_user_images.h" #include "chrome/browser/chromeos/login/login_display.h" #include "chrome/browser/chromeos/login/ownership_service.h" #include "chrome/browser/chromeos/user_cros_settings_provider.h" #include "chrome/browser/chromeos/wm_ipc.h" #include "chrome/browser/defaults.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "content/browser/browser_thread.h" #include "content/common/notification_service.h" #include "content/common/notification_type.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/codec/png_codec.h" namespace chromeos { namespace { // A vector pref of the users who have logged into the device. const char kLoggedInUsers[] = "LoggedInUsers"; // A dictionary that maps usernames to file paths to their images. const char kUserImages[] = "UserImages"; // Incognito user is represented by an empty string (since some code already // depends on that and it's hard to figure out what). const char kGuestUser[] = ""; base::LazyInstance g_user_manager(base::LINKER_INITIALIZED); // Stores path to the image in local state. Runs on UI thread. void SavePathToLocalState(const std::string& username, const std::string& image_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); PrefService* local_state = g_browser_process->local_state(); DictionaryPrefUpdate images_update(local_state, kUserImages); images_update->SetWithoutPathExpansion(username, new StringValue(image_path)); DVLOG(1) << "Saving path to user image in Local State."; local_state->SavePersistentPrefs(); } // Saves image to file with specified path. Runs on FILE thread. // Posts task for saving image path to local state on UI thread. void SaveImageToFile(const SkBitmap& image, const FilePath& image_path, const std::string& username) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::vector encoded_image; if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, true, &encoded_image)) { LOG(ERROR) << "Failed to PNG encode the image."; return; } if (file_util::WriteFile(image_path, reinterpret_cast(&encoded_image[0]), encoded_image.size()) == -1) { LOG(ERROR) << "Failed to save image to file."; return; } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableFunction(&SavePathToLocalState, username, image_path.value())); } // Deletes user's image file. Runs on FILE thread. void DeleteUserImage(const FilePath& image_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); if (!file_util::Delete(image_path, false)) { LOG(ERROR) << "Failed to remove user image."; return; } } // Updates current user ownership on UI thread. void UpdateOwnership(bool is_owner) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); g_user_manager.Get().set_current_user_is_owner(is_owner); NotificationService::current()->Notify(NotificationType::OWNERSHIP_CHECKED, NotificationService::AllSources(), NotificationService::NoDetails()); if (is_owner) { // Also update cached value. UserCrosSettingsProvider::UpdateCachedOwner( g_user_manager.Get().logged_in_user().email()); } } // Checks current user's ownership on file thread. void CheckOwnership() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); bool is_owner = OwnershipService::GetSharedInstance()->CurrentUserIsOwner(); VLOG(1) << "Current user " << (is_owner ? "is owner" : "is not owner"); g_user_manager.Get().set_current_user_is_owner(is_owner); // UserManager should be accessed only on UI thread. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableFunction(&UpdateOwnership, is_owner)); } // Used to handle the asynchronous response of deleting a cryptohome directory. class RemoveAttempt : public CryptohomeLibrary::Delegate { public: // Creates new remove attempt for the given user. Note, |delegate| can // be NULL. RemoveAttempt(const std::string& user_email, chromeos::RemoveUserDelegate* delegate) : user_email_(user_email), delegate_(delegate), method_factory_(this) { RemoveUser(); } void RemoveUser() { // Owner is not allowed to be removed from the device. // Must not proceed without signature verification. UserCrosSettingsProvider user_settings; bool trusted_owner_available = user_settings.RequestTrustedOwner( method_factory_.NewRunnableMethod(&RemoveAttempt::RemoveUser)); if (!trusted_owner_available) { // Value of owner email is still not verified. // Another attempt will be invoked after verification completion. return; } if (user_email_ == UserCrosSettingsProvider::cached_owner()) { // Owner is not allowed to be removed from the device. Probably on // the stack, so deffer the deletion. MessageLoop::current()->DeleteSoon(FROM_HERE, this); return; } if (delegate_) delegate_->OnBeforeUserRemoved(user_email_); chromeos::UserManager::Get()->RemoveUserFromList(user_email_); RemoveUserCryptohome(); if (delegate_) delegate_->OnUserRemoved(user_email_); } void RemoveUserCryptohome() { if (CrosLibrary::Get()->EnsureLoaded()) { CrosLibrary::Get()->GetCryptohomeLibrary()->AsyncRemove(user_email_, this); } } void OnComplete(bool success, int return_code) { // Log the error, but there's not much we can do. if (!success) { VLOG(1) << "Removal of cryptohome for " << user_email_ << " failed, return code: " << return_code; } delete this; } private: std::string user_email_; chromeos::RemoveUserDelegate* delegate_; // Factory of callbacks. ScopedRunnableMethodFactory method_factory_; DISALLOW_COPY_AND_ASSIGN(RemoveAttempt); }; } // namespace UserManager::User::User() { image_ = *ResourceBundle::GetSharedInstance().GetBitmapNamed( kDefaultImageResources[0]); } UserManager::User::~User() {} std::string UserManager::User::GetDisplayName() const { size_t i = email_.find('@'); if (i == 0 || i == std::string::npos) { return email_; } return email_.substr(0, i); } std::string UserManager::User::GetNameTooltip() const { const std::string& user_email = email(); size_t at_pos = user_email.rfind('@'); if (at_pos == std::string::npos) { NOTREACHED(); return std::string(); } size_t domain_start = at_pos + 1; std::string domain = user_email.substr(domain_start, user_email.length() - domain_start); return base::StringPrintf("%s (%s)", GetDisplayName().c_str(), domain.c_str()); } // static UserManager* UserManager::Get() { // Not thread-safe. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); return &g_user_manager.Get(); } // static void UserManager::RegisterPrefs(PrefService* local_state) { local_state->RegisterListPref(kLoggedInUsers); local_state->RegisterDictionaryPref(kUserImages); } std::vector UserManager::GetUsers() const { std::vector users; if (!g_browser_process) return users; PrefService* local_state = g_browser_process->local_state(); const ListValue* prefs_users = local_state->GetList(kLoggedInUsers); const DictionaryValue* prefs_images = local_state->GetDictionary(kUserImages); if (prefs_users) { for (ListValue::const_iterator it = prefs_users->begin(); it != prefs_users->end(); ++it) { std::string email; if ((*it)->GetAsString(&email)) { User user; user.set_email(email); UserImages::const_iterator image_it = user_images_.find(email); std::string image_path; if (image_it == user_images_.end()) { if (prefs_images && prefs_images->GetStringWithoutPathExpansion(email, &image_path)) { int default_image_id = kDefaultImagesCount; if (IsDefaultImagePath(image_path, &default_image_id)) { DCHECK(default_image_id < kDefaultImagesCount); int resource_id = kDefaultImageResources[default_image_id]; user.set_image( *ResourceBundle::GetSharedInstance().GetBitmapNamed( resource_id)); user_images_[email] = user.image(); } else { // Insert the default image so we don't send another request if // GetUsers is called twice. user_images_[email] = user.image(); image_loader_->Start(email, image_path, false); } } } else { user.set_image(image_it->second); } users.push_back(user); } } } return users; } void UserManager::OffTheRecordUserLoggedIn() { user_is_logged_in_ = true; logged_in_user_ = User(); logged_in_user_.set_email(kGuestUser); NotifyOnLogin(); } void UserManager::UserLoggedIn(const std::string& email) { if (email == kGuestUser) { OffTheRecordUserLoggedIn(); return; } if (!IsKnownUser(email)) { current_user_is_new_ = true; browser_defaults::skip_restore = true; } // Get a copy of the current users. std::vector users = GetUsers(); // Clear the prefs view of the users. PrefService* prefs = g_browser_process->local_state(); ListPrefUpdate prefs_users_update(prefs, kLoggedInUsers); prefs_users_update->Clear(); user_is_logged_in_ = true; logged_in_user_ = User(); logged_in_user_.set_email(email); // Make sure this user is first. prefs_users_update->Append(Value::CreateStringValue(email)); for (std::vector::iterator it = users.begin(); it != users.end(); ++it) { std::string user_email = it->email(); // Skip the most recent user. if (email != user_email) { prefs_users_update->Append(Value::CreateStringValue(user_email)); } else { logged_in_user_ = *it; } } prefs->SavePersistentPrefs(); NotifyOnLogin(); if (current_user_is_new_) SetDefaultUserImage(email); } void UserManager::RemoveUser(const std::string& email, RemoveUserDelegate* delegate) { // Get a copy of the current users. std::vector users = GetUsers(); // Sanity check: we must not remove single user. This check may seem // redundant at a first sight because this single user must be an owner and // we perform special check later in order not to remove an owner. However // due to non-instant nature of ownership assignment this later check may // sometimes fail. See http://crosbug.com/12723 if (users.size() < 2) return; bool user_found = false; for (size_t i = 0; !user_found && i < users.size(); ++i) user_found = (email == users[i].email()); if (!user_found) return; // |RemoveAttempt| deletes itself when done. new RemoveAttempt(email, delegate); } void UserManager::RemoveUserFromList(const std::string& email) { // Get a copy of the current users. std::vector users = GetUsers(); // Clear the prefs view of the users. PrefService* prefs = g_browser_process->local_state(); ListPrefUpdate prefs_users_update(prefs, kLoggedInUsers); prefs_users_update->Clear(); for (std::vector::iterator it = users.begin(); it != users.end(); ++it) { std::string user_email = it->email(); // Skip user that we would like to delete. if (email != user_email) prefs_users_update->Append(Value::CreateStringValue(user_email)); } DictionaryPrefUpdate prefs_images_update(prefs, kUserImages); std::string image_path_string; prefs_images_update->GetStringWithoutPathExpansion(email, &image_path_string); prefs_images_update->RemoveWithoutPathExpansion(email, NULL); prefs->SavePersistentPrefs(); int default_image_id = kDefaultImagesCount; if (!IsDefaultImagePath(image_path_string, &default_image_id)) { FilePath image_path(image_path_string); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableFunction(&DeleteUserImage, image_path)); } } bool UserManager::IsKnownUser(const std::string& email) { std::vector users = GetUsers(); for (std::vector::iterator it = users.begin(); it < users.end(); ++it) { if (it->email() == email) return true; } return false; } const UserManager::User& UserManager::logged_in_user() const { return logged_in_user_; } void UserManager::SetLoggedInUserImage(const SkBitmap& image) { if (logged_in_user_.email().empty()) return; OnImageLoaded(logged_in_user_.email(), image, false); } void UserManager::LoadLoggedInUserImage(const FilePath& path) { if (logged_in_user_.email().empty()) return; image_loader_->Start(logged_in_user_.email(), path.value(), true); } void UserManager::SaveUserImage(const std::string& username, const SkBitmap& image) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); FilePath image_path = GetImagePathForUser(username); DVLOG(1) << "Saving user image to " << image_path.value(); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableFunction(&SaveImageToFile, image, image_path, username)); } void UserManager::SaveUserImagePath(const std::string& username, const std::string& image_path) { SavePathToLocalState(username, image_path); } void UserManager::SetDefaultUserImage(const std::string& username) { if (!g_browser_process) return; PrefService* local_state = g_browser_process->local_state(); DCHECK(local_state); const ListValue* prefs_users = local_state->GetList(kLoggedInUsers); DCHECK(prefs_users); const DictionaryValue* prefs_images = local_state->GetDictionary(kUserImages); DCHECK(prefs_images); // We want to distribute default images between users uniformly so that if // there're more users with red image, we won't add red one for sure. // Thus we count how many default images of each color are used and choose // the first color with minimal usage. std::vector colors_count(kDefaultImagesCount, 0); for (ListValue::const_iterator it = prefs_users->begin(); it != prefs_users->end(); ++it) { std::string email; if ((*it)->GetAsString(&email)) { std::string image_path; int default_image_id = kDefaultImagesCount; if (prefs_images->GetStringWithoutPathExpansion(email, &image_path) && IsDefaultImagePath(image_path, &default_image_id)) { DCHECK(default_image_id < kDefaultImagesCount); ++colors_count[default_image_id]; } } } std::vector::const_iterator min_it = std::min_element(colors_count.begin(), colors_count.end()); int selected_id = min_it - colors_count.begin(); std::string user_image_path = GetDefaultImagePath(selected_id); int resource_id = kDefaultImageResources[selected_id]; SkBitmap user_image = *ResourceBundle::GetSharedInstance().GetBitmapNamed( resource_id); SavePathToLocalState(username, user_image_path); SetLoggedInUserImage(user_image); } int UserManager::GetUserDefaultImageIndex(const std::string& username) { if (!g_browser_process) return -1; PrefService* local_state = g_browser_process->local_state(); const DictionaryValue* prefs_images = local_state->GetDictionary(kUserImages); if (!prefs_images) return -1; std::string image_path; if (!prefs_images->GetStringWithoutPathExpansion(username, &image_path)) return -1; int image_id = kDefaultImagesCount; if (!IsDefaultImagePath(image_path, &image_id)) return -1; return image_id; } void UserManager::OnImageLoaded(const std::string& username, const SkBitmap& image, bool should_save_image) { DVLOG(1) << "Loaded image for " << username; user_images_[username] = image; User user; user.set_email(username); user.set_image(image); if (logged_in_user_.email() == username) logged_in_user_.set_image(image); if (should_save_image) SaveUserImage(username, image); NotificationService::current()->Notify( NotificationType::LOGIN_USER_IMAGE_CHANGED, Source(this), Details(&user)); } bool UserManager::IsLoggedInAsGuest() const { return logged_in_user().email() == kGuestUser; } // Private constructor and destructor. Do nothing. UserManager::UserManager() : ALLOW_THIS_IN_INITIALIZER_LIST(image_loader_(new UserImageLoader(this))), current_user_is_owner_(false), current_user_is_new_(false), user_is_logged_in_(false) { registrar_.Add(this, NotificationType::OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED, NotificationService::AllSources()); } UserManager::~UserManager() { image_loader_->set_delegate(NULL); } FilePath UserManager::GetImagePathForUser(const std::string& username) { std::string filename = username + ".png"; FilePath user_data_dir; PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); return user_data_dir.AppendASCII(filename); } class RealTPMTokenInfoDelegate : public crypto::TPMTokenInfoDelegate { public: RealTPMTokenInfoDelegate(); virtual ~RealTPMTokenInfoDelegate(); virtual bool IsTokenReady() const; virtual void GetTokenInfo(std::string* token_name, std::string* user_pin) const; }; RealTPMTokenInfoDelegate::RealTPMTokenInfoDelegate() {} RealTPMTokenInfoDelegate::~RealTPMTokenInfoDelegate() {} bool RealTPMTokenInfoDelegate::IsTokenReady() const { return CrosLibrary::Get()->GetCryptohomeLibrary()->Pkcs11IsTpmTokenReady(); } void RealTPMTokenInfoDelegate::GetTokenInfo(std::string* token_name, std::string* user_pin) const { std::string local_token_name; std::string local_user_pin; CrosLibrary::Get()->GetCryptohomeLibrary()->Pkcs11GetTpmTokenInfo( &local_token_name, &local_user_pin); if (token_name) *token_name = local_token_name; if (user_pin) *user_pin = local_user_pin; } void UserManager::NotifyOnLogin() { NotificationService::current()->Notify( NotificationType::LOGIN_USER_CHANGED, Source(this), Details(&logged_in_user_)); chromeos::CrosLibrary::Get()->GetInputMethodLibrary()-> SetDeferImeStartup(false); // Shut down the IME so that it will reload the user's settings. chromeos::CrosLibrary::Get()->GetInputMethodLibrary()-> StopInputMethodDaemon(); // Let the window manager know that we're logged in now. WmIpc::instance()->SetLoggedInProperty(true); // Ensure we've opened the real user's key/certificate database. crypto::OpenPersistentNSSDB(); // Only load the Opencryptoki library into NSS if we have this switch. // TODO(gspencer): Remove this switch once cryptohomed work is finished: // http://crosbug.com/12295 and http://crosbug.com/12304 if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kLoadOpencryptoki)) { crypto::EnableTPMTokenForNSS(new RealTPMTokenInfoDelegate()); } // Schedules current user ownership check on file thread. BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, NewRunnableFunction(&CheckOwnership)); } void UserManager::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, NewRunnableFunction(&CheckOwnership)); } } bool UserManager::current_user_is_owner() const { base::AutoLock lk(current_user_is_owner_lock_); return current_user_is_owner_; } void UserManager::set_current_user_is_owner(bool current_user_is_owner) { base::AutoLock lk(current_user_is_owner_lock_); current_user_is_owner_ = current_user_is_owner; } } // namespace chromeos