blob: 8561504d7f5c18da08210027a64f2c63521239ab [file] [log] [blame]
// Copyright 2013 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/app_mode/startup_app_launcher.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/json/json_file_value_serializer.h"
#include "base/path_service.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/app_mode/app_session_lifetime.h"
#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/updater/manifest_fetch_data.h"
#include "chrome/browser/extensions/updater/safe_manifest_parser.h"
#include "chrome/browser/extensions/webstore_startup_installer.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/signin/profile_oauth2_token_service.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
#include "google_apis/gaia/gaia_auth_consumer.h"
#include "google_apis/gaia/gaia_constants.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
#include "url/gurl.h"
using content::BrowserThread;
using extensions::Extension;
using extensions::WebstoreStartupInstaller;
namespace chromeos {
namespace {
const char kOAuthRefreshToken[] = "refresh_token";
const char kOAuthClientId[] = "client_id";
const char kOAuthClientSecret[] = "client_secret";
const base::FilePath::CharType kOAuthFileName[] =
FILE_PATH_LITERAL("kiosk_auth");
} // namespace
class StartupAppLauncher::AppUpdateChecker
: public base::SupportsWeakPtr<AppUpdateChecker>,
public net::URLFetcherDelegate {
public:
explicit AppUpdateChecker(StartupAppLauncher* launcher)
: launcher_(launcher),
profile_(launcher->profile_),
app_id_(launcher->app_id_) {}
virtual ~AppUpdateChecker() {}
void Start() {
const Extension* app = GetInstalledApp();
if (!app) {
launcher_->OnUpdateCheckNotInstalled();
return;
}
GURL update_url = extensions::ManifestURL::GetUpdateURL(app);
if (update_url.is_empty())
update_url = extension_urls::GetWebstoreUpdateUrl();
if (!update_url.is_valid()) {
launcher_->OnUpdateCheckNoUpdate();
return;
}
manifest_fetch_data_.reset(
new extensions::ManifestFetchData(update_url, 0));
manifest_fetch_data_->AddExtension(
app_id_, app->version()->GetString(), NULL, "", "");
manifest_fetcher_.reset(net::URLFetcher::Create(
manifest_fetch_data_->full_url(), net::URLFetcher::GET, this));
manifest_fetcher_->SetRequestContext(profile_->GetRequestContext());
manifest_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DISABLE_CACHE);
manifest_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
manifest_fetcher_->Start();
}
private:
const Extension* GetInstalledApp() {
ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
return extension_service->GetInstalledExtension(app_id_);
}
void HandleManifestResults(const extensions::ManifestFetchData& fetch_data,
const UpdateManifest::Results* results) {
if (!results || results->list.empty()) {
launcher_->OnUpdateCheckNoUpdate();
return;
}
DCHECK_EQ(1u, results->list.size());
const UpdateManifest::Result& update = results->list[0];
if (update.browser_min_version.length() > 0) {
Version browser_version;
chrome::VersionInfo version_info;
if (version_info.is_valid())
browser_version = Version(version_info.Version());
Version browser_min_version(update.browser_min_version);
if (browser_version.IsValid() &&
browser_min_version.IsValid() &&
browser_min_version.CompareTo(browser_version) > 0) {
launcher_->OnUpdateCheckNoUpdate();
return;
}
}
const Version& existing_version = *GetInstalledApp()->version();
Version update_version(update.version);
if (existing_version.IsValid() &&
update_version.IsValid() &&
update_version.CompareTo(existing_version) <= 0) {
launcher_->OnUpdateCheckNoUpdate();
return;
}
launcher_->OnUpdateCheckUpdateAvailable();
}
// net::URLFetcherDelegate implementation.
virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
DCHECK_EQ(source, manifest_fetcher_.get());
if (source->GetStatus().status() != net::URLRequestStatus::SUCCESS ||
source->GetResponseCode() != 200) {
launcher_->OnUpdateCheckNoUpdate();
return;
}
std::string data;
source->GetResponseAsString(&data);
scoped_refptr<extensions::SafeManifestParser> safe_parser(
new extensions::SafeManifestParser(
data,
manifest_fetch_data_.release(),
base::Bind(&AppUpdateChecker::HandleManifestResults,
AsWeakPtr())));
safe_parser->Start();
}
StartupAppLauncher* launcher_;
Profile* profile_;
const std::string app_id_;
scoped_ptr<extensions::ManifestFetchData> manifest_fetch_data_;
scoped_ptr<net::URLFetcher> manifest_fetcher_;
DISALLOW_COPY_AND_ASSIGN(AppUpdateChecker);
};
StartupAppLauncher::StartupAppLauncher(Profile* profile,
const std::string& app_id,
StartupAppLauncher::Delegate* delegate)
: profile_(profile),
app_id_(app_id),
delegate_(delegate),
install_attempted_(false),
ready_to_launch_(false) {
DCHECK(profile_);
DCHECK(Extension::IdIsValid(app_id_));
}
StartupAppLauncher::~StartupAppLauncher() {
// StartupAppLauncher can be deleted at anytime during the launch process
// through a user bailout shortcut.
ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
->RemoveObserver(this);
}
void StartupAppLauncher::Initialize() {
StartLoadingOAuthFile();
}
void StartupAppLauncher::ContinueWithNetworkReady() {
// Starts install if it is not started.
if (!install_attempted_) {
install_attempted_ = true;
MaybeInstall();
}
}
void StartupAppLauncher::StartLoadingOAuthFile() {
delegate_->OnLoadingOAuthFile();
KioskOAuthParams* auth_params = new KioskOAuthParams();
BrowserThread::PostBlockingPoolTaskAndReply(
FROM_HERE,
base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool,
auth_params),
base::Bind(&StartupAppLauncher::OnOAuthFileLoaded,
AsWeakPtr(),
base::Owned(auth_params)));
}
// static.
void StartupAppLauncher::LoadOAuthFileOnBlockingPool(
KioskOAuthParams* auth_params) {
int error_code = JSONFileValueSerializer::JSON_NO_ERROR;
std::string error_msg;
base::FilePath user_data_dir;
CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
base::FilePath auth_file = user_data_dir.Append(kOAuthFileName);
scoped_ptr<JSONFileValueSerializer> serializer(
new JSONFileValueSerializer(user_data_dir.Append(kOAuthFileName)));
scoped_ptr<base::Value> value(
serializer->Deserialize(&error_code, &error_msg));
base::DictionaryValue* dict = NULL;
if (error_code != JSONFileValueSerializer::JSON_NO_ERROR ||
!value.get() || !value->GetAsDictionary(&dict)) {
LOG(WARNING) << "Can't find auth file at " << auth_file.value();
return;
}
dict->GetString(kOAuthRefreshToken, &auth_params->refresh_token);
dict->GetString(kOAuthClientId, &auth_params->client_id);
dict->GetString(kOAuthClientSecret, &auth_params->client_secret);
}
void StartupAppLauncher::OnOAuthFileLoaded(KioskOAuthParams* auth_params) {
auth_params_ = *auth_params;
// Override chrome client_id and secret that will be used for identity
// API token minting.
if (!auth_params_.client_id.empty() && !auth_params_.client_secret.empty()) {
UserManager::Get()->SetAppModeChromeClientOAuthInfo(
auth_params_.client_id,
auth_params_.client_secret);
}
// If we are restarting chrome (i.e. on crash), we need to initialize
// OAuth2TokenService as well.
InitializeTokenService();
}
void StartupAppLauncher::InitializeNetwork() {
delegate_->InitializeNetwork();
}
void StartupAppLauncher::InitializeTokenService() {
delegate_->OnInitializingTokenService();
ProfileOAuth2TokenService* profile_token_service =
ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
if (profile_token_service->RefreshTokenIsAvailable(
profile_token_service->GetPrimaryAccountId()) ||
auth_params_.refresh_token.empty()) {
InitializeNetwork();
} else {
// Pass oauth2 refresh token from the auth file.
// TODO(zelidrag): We should probably remove this option after M27.
// TODO(fgorski): This can go when we have persistence implemented on PO2TS.
// Unless the code is no longer needed.
// TODO(rogerta): Now that this CL implements token persistence in PO2TS, is
// this code still needed? See above two TODOs.
//
// ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or
// OnRefreshTokensLoaded. Given that we want to handle exactly one event,
// whichever comes first, both handlers call RemoveObserver on PO2TS.
// Handling any of the two events is the only way to resume the execution
// and enable Cleanup method to be called, self-invoking a destructor.
profile_token_service->AddObserver(this);
profile_token_service->UpdateCredentials(
"kiosk_mode@localhost",
auth_params_.refresh_token);
}
}
void StartupAppLauncher::OnRefreshTokenAvailable(
const std::string& account_id) {
ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
->RemoveObserver(this);
InitializeNetwork();
}
void StartupAppLauncher::OnRefreshTokensLoaded() {
ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
->RemoveObserver(this);
InitializeNetwork();
}
void StartupAppLauncher::LaunchApp() {
if (!ready_to_launch_) {
NOTREACHED();
LOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
}
const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
extension_service()->GetInstalledExtension(app_id_);
CHECK(extension);
if (!extensions::KioskModeInfo::IsKioskEnabled(extension)) {
OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED);
return;
}
// Always open the app in a window.
OpenApplication(AppLaunchParams(profile_, extension,
extensions::LAUNCH_CONTAINER_WINDOW,
NEW_WINDOW));
InitAppSession(profile_, app_id_);
UserManager::Get()->SessionStarted();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_KIOSK_APP_LAUNCHED,
content::NotificationService::AllSources(),
content::NotificationService::NoDetails());
OnLaunchSuccess();
}
void StartupAppLauncher::OnLaunchSuccess() {
delegate_->OnLaunchSucceeded();
}
void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) {
LOG(ERROR) << "App launch failed, error: " << error;
DCHECK_NE(KioskAppLaunchError::NONE, error);
delegate_->OnLaunchFailed(error);
}
void StartupAppLauncher::MaybeInstall() {
delegate_->OnInstallingApp();
update_checker_.reset(new AppUpdateChecker(this));
update_checker_->Start();
}
void StartupAppLauncher::OnUpdateCheckNotInstalled() {
BeginInstall();
}
void StartupAppLauncher::OnUpdateCheckUpdateAvailable() {
// Uninstall to force a re-install.
// TODO(xiyuan): Find a better way. Either download CRX and install it
// directly or integrate with ExtensionUpdater in someway.
ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
extension_service->UninstallExtension(app_id_, false, NULL);
OnUpdateCheckNotInstalled();
}
void StartupAppLauncher::OnUpdateCheckNoUpdate() {
OnReadyToLaunch();
}
void StartupAppLauncher::BeginInstall() {
installer_ = new WebstoreStartupInstaller(
app_id_,
profile_,
false,
base::Bind(&StartupAppLauncher::InstallCallback, AsWeakPtr()));
installer_->BeginInstall();
}
void StartupAppLauncher::InstallCallback(bool success,
const std::string& error) {
installer_ = NULL;
if (success) {
// Finish initialization after the callback returns.
// So that the app finishes its installation.
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&StartupAppLauncher::OnReadyToLaunch,
AsWeakPtr()));
// Schedule app data update after installation.
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&StartupAppLauncher::UpdateAppData,
AsWeakPtr()));
return;
}
LOG(ERROR) << "App install failed: " << error;
OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
}
void StartupAppLauncher::OnReadyToLaunch() {
ready_to_launch_ = true;
delegate_->OnReadyToLaunch();
}
void StartupAppLauncher::UpdateAppData() {
KioskAppManager::Get()->ClearAppData(app_id_);
KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_, profile_, NULL);
}
} // namespace chromeos