// 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