普通文本  |  598行  |  21.61 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/login_utils.h"

#include <vector>

#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread_restrictions.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/boot_times_loader.h"
#include "chrome/browser/chromeos/cros/login_library.h"
#include "chrome/browser/chromeos/cros/network_library.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/login/background_view.h"
#include "chrome/browser/chromeos/login/cookie_fetcher.h"
#include "chrome/browser/chromeos/login/google_authenticator.h"
#include "chrome/browser/chromeos/login/language_switch_menu.h"
#include "chrome/browser/chromeos/login/ownership_service.h"
#include "chrome/browser/chromeos/login/parallel_authenticator.h"
#include "chrome/browser/chromeos/login/user_image_downloader.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/proxy_config_service.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/net/chrome_url_request_context.h"
#include "chrome/browser/net/gaia/token_service.h"
#include "chrome/browser/net/preconnect.h"
#include "chrome/browser/net/pref_proxy_config_service.h"
#include "chrome/browser/plugin_updater.h"
#include "chrome/browser/prefs/pref_member.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/ui/browser_init.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/logging_chrome.h"
#include "chrome/common/net/gaia/gaia_auth_fetcher.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "content/browser/browser_thread.h"
#include "googleurl/src/gurl.h"
#include "net/base/cookie_store.h"
#include "net/proxy/proxy_config_service.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "views/widget/widget_gtk.h"
#include "ui/gfx/gl/gl_switches.h"

namespace chromeos {

namespace {

// Affixes for Auth token received from ClientLogin request.
const char kAuthPrefix[] = "Auth=";
const char kAuthSuffix[] = "\n";

// Increase logging level for Guest mode to avoid LOG(INFO) messages in logs.
const char kGuestModeLoggingLevel[] = "1";

// Format of command line switch.
const char kSwitchFormatString[] = " --%s=\"%s\"";

// User name which is used in the Guest session.
const char kGuestUserName[] = "";

// Resets the proxy configuration service for the default request context.
class ResetDefaultProxyConfigServiceTask : public Task {
 public:
  ResetDefaultProxyConfigServiceTask(
      net::ProxyConfigService* proxy_config_service)
      : proxy_config_service_(proxy_config_service) {}
  virtual ~ResetDefaultProxyConfigServiceTask() {}

  // Task override.
  virtual void Run() {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    net::URLRequestContextGetter* getter = Profile::GetDefaultRequestContext();
    DCHECK(getter);
    if (getter) {
      getter->GetURLRequestContext()->proxy_service()->ResetConfigService(
          proxy_config_service_.release());
    }
  }

 private:
  scoped_ptr<net::ProxyConfigService> proxy_config_service_;

  DISALLOW_COPY_AND_ASSIGN(ResetDefaultProxyConfigServiceTask);
};

}  // namespace

class LoginUtilsImpl : public LoginUtils,
                       public ProfileManager::Observer {
 public:
  LoginUtilsImpl()
      : background_view_(NULL) {
  }

  virtual void PrepareProfile(
      const std::string& username,
      const std::string& password,
      const GaiaAuthConsumer::ClientLoginResult& credentials,
      bool pending_requests,
      LoginUtils::Delegate* delegate);

  // Invoked after the tmpfs is successfully mounted.
  // Launches a browser in the incognito mode.
  virtual void CompleteOffTheRecordLogin(const GURL& start_url);

  // Invoked when the user is logging in for the first time, or is logging in as
  // a guest user.
  virtual void SetFirstLoginPrefs(PrefService* prefs);

  // Creates and returns the authenticator to use. The caller owns the returned
  // Authenticator and must delete it when done.
  virtual Authenticator* CreateAuthenticator(LoginStatusConsumer* consumer);

  // Warms the url used by authentication.
  virtual void PrewarmAuthentication();

  // Given the credentials try to exchange them for
  // full-fledged Google authentication cookies.
  virtual void FetchCookies(
      Profile* profile,
      const GaiaAuthConsumer::ClientLoginResult& credentials);

  // Supply credentials for sync and others to use.
  virtual void FetchTokens(
      Profile* profile,
      const GaiaAuthConsumer::ClientLoginResult& credentials);

  // Sets the current background view.
  virtual void SetBackgroundView(chromeos::BackgroundView* background_view);

  // Gets the current background view.
  virtual chromeos::BackgroundView* GetBackgroundView();

  // ProfileManager::Observer implementation:
  virtual void OnProfileCreated(Profile* profile);

 protected:
  virtual std::string GetOffTheRecordCommandLine(
      const GURL& start_url,
      const CommandLine& base_command_line,
      CommandLine *command_line);

 private:
  // Check user's profile for kApplicationLocale setting.
  void RespectLocalePreference(Profile* pref);

  // The current background view.
  chromeos::BackgroundView* background_view_;

  std::string username_;
  std::string password_;
  GaiaAuthConsumer::ClientLoginResult credentials_;
  bool pending_requests_;

  // Delegate to be fired when the profile will be prepared.
  LoginUtils::Delegate* delegate_;

  DISALLOW_COPY_AND_ASSIGN(LoginUtilsImpl);
};

class LoginUtilsWrapper {
 public:
  static LoginUtilsWrapper* GetInstance() {
    return Singleton<LoginUtilsWrapper>::get();
  }

  LoginUtils* get() {
    base::AutoLock create(create_lock_);
    if (!ptr_.get())
      reset(new LoginUtilsImpl);
    return ptr_.get();
  }

  void reset(LoginUtils* ptr) {
    ptr_.reset(ptr);
  }

 private:
  friend struct DefaultSingletonTraits<LoginUtilsWrapper>;

  LoginUtilsWrapper() {}

  base::Lock create_lock_;
  scoped_ptr<LoginUtils> ptr_;

  DISALLOW_COPY_AND_ASSIGN(LoginUtilsWrapper);
};

void LoginUtilsImpl::PrepareProfile(
    const std::string& username,
    const std::string& password,
    const GaiaAuthConsumer::ClientLoginResult& credentials,
    bool pending_requests,
    LoginUtils::Delegate* delegate) {
  BootTimesLoader* btl = BootTimesLoader::Get();

  VLOG(1) << "Completing login for " << username;
  btl->AddLoginTimeMarker("CompletingLogin", false);

  if (CrosLibrary::Get()->EnsureLoaded()) {
    CrosLibrary::Get()->GetLoginLibrary()->StartSession(username, "");
    btl->AddLoginTimeMarker("StartedSession", false);
  }

  UserManager::Get()->UserLoggedIn(username);
  btl->AddLoginTimeMarker("UserLoggedIn", false);

  // Switch log file as soon as possible.
  logging::RedirectChromeLogging(*(CommandLine::ForCurrentProcess()));
  btl->AddLoginTimeMarker("LoggingRedirected", false);

  username_ = username;
  password_ = password;
  credentials_ = credentials;
  pending_requests_ = pending_requests;
  delegate_ = delegate;

  // The default profile will have been changed because the ProfileManager
  // will process the notification that the UserManager sends out.
  ProfileManager::CreateDefaultProfileAsync(this);
}

void LoginUtilsImpl::OnProfileCreated(Profile* profile) {
  CHECK(profile);

  BootTimesLoader* btl = BootTimesLoader::Get();
  btl->AddLoginTimeMarker("UserProfileGotten", false);

  // Change the proxy configuration service of the default request context to
  // use the preference configuration from the logged-in profile. This ensures
  // that requests done through the default context use the proxy configuration
  // provided by configuration policy.
  //
  // Note: Many of the clients of the default request context should probably be
  // fixed to use the request context of the profile they are associated with.
  // This includes preconnect, autofill, metrics service to only name a few;
  // see http://code.google.com/p/chromium/issues/detail?id=64339 for details.
  //
  // TODO(mnissler) Revisit when support for device-specific policy arrives, at
  // which point the default request context can directly be managed through
  // device policy.
  net::ProxyConfigService* proxy_config_service =
      new PrefProxyConfigService(
          profile->GetProxyConfigTracker(),
          new chromeos::ProxyConfigService(
              g_browser_process->chromeos_proxy_config_service_impl()));
  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                          new ResetDefaultProxyConfigServiceTask(
                              proxy_config_service));

  // Since we're doing parallel authentication, only new user sign in
  // would perform online auth before calling PrepareProfile.
  // For existing users there's usually a pending online auth request.
  // Cookies will be fetched after it's is succeeded.
  if (!pending_requests_) {
    FetchCookies(profile, credentials_);
  }

  // Init extension event routers; this normally happens in browser_main
  // but on Chrome OS it has to be deferred until the user finishes
  // logging in and the profile is not OTR.
  if (profile->GetExtensionService() &&
      profile->GetExtensionService()->extensions_enabled()) {
    profile->GetExtensionService()->InitEventRouters();
  }
  btl->AddLoginTimeMarker("ExtensionsServiceStarted", false);

  // Supply credentials for sync and others to use. Load tokens from disk.
  TokenService* token_service = profile->GetTokenService();
  token_service->Initialize(GaiaConstants::kChromeOSSource,
                            profile);
  token_service->LoadTokensFromDB();

  // For existing users there's usually a pending online auth request.
  // Tokens will be fetched after it's is succeeded.
  if (!pending_requests_) {
    FetchTokens(profile, credentials_);
  }
  btl->AddLoginTimeMarker("TokensGotten", false);

  // Set the CrOS user by getting this constructor run with the
  // user's email on first retrieval.
  profile->GetProfileSyncService(username_)->SetPassphrase(password_,
                                                           false,
                                                           true);
  btl->AddLoginTimeMarker("SyncStarted", false);

  // Own TPM device if, for any reason, it has not been done in EULA
  // wizard screen.
  if (CrosLibrary::Get()->EnsureLoaded()) {
    CryptohomeLibrary* cryptohome = CrosLibrary::Get()->GetCryptohomeLibrary();
    if (cryptohome->TpmIsEnabled() && !cryptohome->TpmIsBeingOwned()) {
      if (cryptohome->TpmIsOwned()) {
        cryptohome->TpmClearStoredPassword();
      } else {
        cryptohome->TpmCanAttemptOwnership();
      }
    }
  }
  btl->AddLoginTimeMarker("TPMOwned", false);

  RespectLocalePreference(profile);

  if (UserManager::Get()->current_user_is_new()) {
    SetFirstLoginPrefs(profile->GetPrefs());
  }

  // Enable/disable plugins based on user preferences.
  PluginUpdater::GetInstance()->UpdatePluginGroupsStateFromPrefs(profile);
  btl->AddLoginTimeMarker("PluginsStateUpdated", false);

  // We suck. This is a hack since we do not have the enterprise feature
  // done yet to pull down policies from the domain admin. We'll take this
  // out when we get that done properly.
  // TODO(xiyuan): Remove this once enterprise feature is ready.
  if (EndsWith(username_, "@google.com", true)) {
    PrefService* pref_service = profile->GetPrefs();
    pref_service->SetBoolean(prefs::kEnableScreenLock, true);
  }

  profile->OnLogin();

  delegate_->OnProfilePrepared(profile);

  // TODO(altimofeev): Need to sanitize memory used to store password.
  password_ = "";
  username_ = "";
  credentials_ = GaiaAuthConsumer::ClientLoginResult();
}

void LoginUtilsImpl::FetchCookies(
    Profile* profile,
    const GaiaAuthConsumer::ClientLoginResult& credentials) {
  // Take the credentials passed in and try to exchange them for
  // full-fledged Google authentication cookies.  This is
  // best-effort; it's possible that we'll fail due to network
  // troubles or some such.
  // CookieFetcher will delete itself once done.
  CookieFetcher* cf = new CookieFetcher(profile);
  cf->AttemptFetch(credentials.data);
  BootTimesLoader::Get()->AddLoginTimeMarker("CookieFetchStarted", false);
}

void LoginUtilsImpl::FetchTokens(
    Profile* profile,
    const GaiaAuthConsumer::ClientLoginResult& credentials) {
  TokenService* token_service = profile->GetTokenService();
  token_service->UpdateCredentials(credentials);
  if (token_service->AreCredentialsValid()) {
    token_service->StartFetchingTokens();
  }
}

void LoginUtilsImpl::RespectLocalePreference(Profile* profile) {
  DCHECK(profile != NULL);
  PrefService* prefs = profile->GetPrefs();
  DCHECK(prefs != NULL);
  if (g_browser_process == NULL)
    return;

  std::string pref_locale = prefs->GetString(prefs::kApplicationLocale);
  if (pref_locale.empty())
    pref_locale = prefs->GetString(prefs::kApplicationLocaleBackup);
  if (pref_locale.empty())
    pref_locale = g_browser_process->GetApplicationLocale();
  DCHECK(!pref_locale.empty());
  profile->ChangeAppLocale(pref_locale, Profile::APP_LOCALE_CHANGED_VIA_LOGIN);
  // Here we don't enable keyboard layouts. Input methods are set up when
  // the user first logs in. Then the user may customize the input methods.
  // Hence changing input methods here, just because the user's UI language
  // is different from the login screen UI language, is not desirable. Note
  // that input method preferences are synced, so users can use their
  // farovite input methods as soon as the preferences are synced.
  LanguageSwitchMenu::SwitchLanguage(pref_locale);
}

void LoginUtilsImpl::CompleteOffTheRecordLogin(const GURL& start_url) {
  VLOG(1) << "Completing incognito login";

  UserManager::Get()->OffTheRecordUserLoggedIn();

  if (CrosLibrary::Get()->EnsureLoaded()) {
    // For guest session we ask session manager to restart Chrome with --bwsi
    // flag. We keep only some of the arguments of this process.
    const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
    CommandLine command_line(browser_command_line.GetProgram());
    std::string cmd_line_str =
        GetOffTheRecordCommandLine(start_url,
                                   browser_command_line,
                                   &command_line);

    CrosLibrary::Get()->GetLoginLibrary()->RestartJob(getpid(), cmd_line_str);
  }
}

std::string LoginUtilsImpl::GetOffTheRecordCommandLine(
    const GURL& start_url,
    const CommandLine& base_command_line,
    CommandLine* command_line) {
  static const char* kForwardSwitches[] = {
      switches::kEnableLogging,
      switches::kEnableAcceleratedPlugins,
      switches::kUseGL,
      switches::kUserDataDir,
      switches::kScrollPixels,
      switches::kEnableGView,
      switches::kNoFirstRun,
      switches::kLoginProfile,
      switches::kCompressSystemFeedback,
      switches::kDisableSeccompSandbox,
      switches::kPpapiFlashInProcess,
      switches::kPpapiFlashPath,
      switches::kPpapiFlashVersion,
#if defined(HAVE_XINPUT2)
      switches::kTouchDevices,
#endif
  };
  command_line->CopySwitchesFrom(base_command_line,
                                 kForwardSwitches,
                                 arraysize(kForwardSwitches));
  command_line->AppendSwitch(switches::kGuestSession);
  command_line->AppendSwitch(switches::kIncognito);
  command_line->AppendSwitchASCII(switches::kLoggingLevel,
                                 kGuestModeLoggingLevel);

  command_line->AppendSwitchASCII(switches::kLoginUser, kGuestUserName);

  if (start_url.is_valid())
    command_line->AppendArg(start_url.spec());

  // Override the value of the homepage that is set in first run mode.
  // TODO(altimofeev): extend action of the |kNoFirstRun| to cover this case.
  command_line->AppendSwitchASCII(
      switches::kHomePage,
      GURL(chrome::kChromeUINewTabURL).spec());

  std::string cmd_line_str = command_line->command_line_string();
  // Special workaround for the arguments that should be quoted.
  // Copying switches won't be needed when Guest mode won't need restart
  // http://crosbug.com/6924
  if (base_command_line.HasSwitch(switches::kRegisterPepperPlugins)) {
    cmd_line_str += base::StringPrintf(
        kSwitchFormatString,
        switches::kRegisterPepperPlugins,
        base_command_line.GetSwitchValueNative(
            switches::kRegisterPepperPlugins).c_str());
  }

  return cmd_line_str;
}

void LoginUtilsImpl::SetFirstLoginPrefs(PrefService* prefs) {
  VLOG(1) << "Setting first login prefs";
  BootTimesLoader* btl = BootTimesLoader::Get();
  std::string locale = g_browser_process->GetApplicationLocale();

  // First, we'll set kLanguagePreloadEngines.
  InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
  std::vector<std::string> input_method_ids;
  input_method::GetFirstLoginInputMethodIds(locale,
                                            library->current_input_method(),
                                            &input_method_ids);
  // Save the input methods in the user's preferences.
  StringPrefMember language_preload_engines;
  language_preload_engines.Init(prefs::kLanguagePreloadEngines,
                                prefs, NULL);
  language_preload_engines.SetValue(JoinString(input_method_ids, ','));
  btl->AddLoginTimeMarker("IMEStarted", false);

  // Second, we'll set kLanguagePreferredLanguages.
  std::vector<std::string> language_codes;
  // The current locale should be on the top.
  language_codes.push_back(locale);

  // Add input method IDs based on the input methods, as there may be
  // input methods that are unrelated to the current locale. Example: the
  // hardware keyboard layout xkb:us::eng is used for logging in, but the
  // UI language is set to French. In this case, we should set "fr,en"
  // to the preferred languages preference.
  std::vector<std::string> candidates;
  input_method::GetLanguageCodesFromInputMethodIds(
      input_method_ids, &candidates);
  for (size_t i = 0; i < candidates.size(); ++i) {
    const std::string& candidate = candidates[i];
    // Skip if it's already in language_codes.
    if (std::count(language_codes.begin(), language_codes.end(),
                   candidate) == 0) {
      language_codes.push_back(candidate);
    }
  }
  // Save the preferred languages in the user's preferences.
  StringPrefMember language_preferred_languages;
  language_preferred_languages.Init(prefs::kLanguagePreferredLanguages,
                                    prefs, NULL);
  language_preferred_languages.SetValue(JoinString(language_codes, ','));
  prefs->ScheduleSavePersistentPrefs();
}

Authenticator* LoginUtilsImpl::CreateAuthenticator(
    LoginStatusConsumer* consumer) {
  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kParallelAuth))
    return new ParallelAuthenticator(consumer);
  else
    return new GoogleAuthenticator(consumer);
}

// We use a special class for this so that it can be safely leaked if we
// never connect. At shutdown the order is not well defined, and it's possible
// for the infrastructure needed to unregister might be unstable and crash.
class WarmingObserver : public NetworkLibrary::NetworkManagerObserver {
 public:
  WarmingObserver() {
    NetworkLibrary *netlib = CrosLibrary::Get()->GetNetworkLibrary();
    netlib->AddNetworkManagerObserver(this);
  }

  // If we're now connected, prewarm the auth url.
  void OnNetworkManagerChanged(NetworkLibrary* netlib) {
    if (netlib->Connected()) {
      const int kConnectionsNeeded = 1;
      chrome_browser_net::PreconnectOnUIThread(
          GURL(GaiaAuthFetcher::kClientLoginUrl),
          chrome_browser_net::UrlInfo::EARLY_LOAD_MOTIVATED,
          kConnectionsNeeded);
      netlib->RemoveNetworkManagerObserver(this);
      delete this;
    }
  }
};

void LoginUtilsImpl::PrewarmAuthentication() {
  if (CrosLibrary::Get()->EnsureLoaded()) {
    NetworkLibrary *network = CrosLibrary::Get()->GetNetworkLibrary();
    if (network->Connected()) {
      const int kConnectionsNeeded = 1;
      chrome_browser_net::PreconnectOnUIThread(
          GURL(GaiaAuthFetcher::kClientLoginUrl),
          chrome_browser_net::UrlInfo::EARLY_LOAD_MOTIVATED,
          kConnectionsNeeded);
    } else {
      new WarmingObserver();
    }
  }
}

void LoginUtilsImpl::SetBackgroundView(BackgroundView* background_view) {
  background_view_ = background_view;
}

BackgroundView* LoginUtilsImpl::GetBackgroundView() {
  return background_view_;
}

LoginUtils* LoginUtils::Get() {
  return LoginUtilsWrapper::GetInstance()->get();
}

void LoginUtils::Set(LoginUtils* mock) {
  LoginUtilsWrapper::GetInstance()->reset(mock);
}

void LoginUtils::DoBrowserLaunch(Profile* profile) {
  BootTimesLoader::Get()->AddLoginTimeMarker("BrowserLaunched", false);

  // Update command line in case loose values were added.
  CommandLine::ForCurrentProcess()->InitFromArgv(
      CommandLine::ForCurrentProcess()->argv());

  VLOG(1) << "Launching browser...";
  BrowserInit browser_init;
  int return_code;
  browser_init.LaunchBrowser(*CommandLine::ForCurrentProcess(),
                             profile,
                             FilePath(),
                             true,
                             &return_code);
}

}  // namespace chromeos