普通文本  |  628行  |  20.88 KB

// 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<UserManager> 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<unsigned char> 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<char*>(&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<RemoveAttempt> 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::User> UserManager::GetUsers() const {
  std::vector<User> 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<User> 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<User>::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<User> 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<User> 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<User>::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<User> users = GetUsers();
  for (std::vector<User>::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<int> 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<int>::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<UserManager>(this),
      Details<const User>(&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<UserManager>(this),
      Details<const User>(&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