Home | History | Annotate | Download | only in app_mode
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/app_mode/startup_app_launcher.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/files/file_path.h"
      9 #include "base/json/json_file_value_serializer.h"
     10 #include "base/path_service.h"
     11 #include "base/time/time.h"
     12 #include "base/values.h"
     13 #include "chrome/browser/chrome_notification_types.h"
     14 #include "chrome/browser/chromeos/app_mode/app_session_lifetime.h"
     15 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
     16 #include "chrome/browser/chromeos/app_mode/kiosk_diagnosis_runner.h"
     17 #include "chrome/browser/chromeos/login/users/user_manager.h"
     18 #include "chrome/browser/extensions/extension_service.h"
     19 #include "chrome/browser/extensions/updater/extension_updater.h"
     20 #include "chrome/browser/extensions/webstore_startup_installer.h"
     21 #include "chrome/browser/lifetime/application_lifetime.h"
     22 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
     23 #include "chrome/browser/signin/signin_manager_factory.h"
     24 #include "chrome/browser/ui/extensions/application_launch.h"
     25 #include "chrome/common/chrome_paths.h"
     26 #include "chrome/common/chrome_switches.h"
     27 #include "chrome/common/chrome_version_info.h"
     28 #include "chrome/common/extensions/manifest_url_handler.h"
     29 #include "components/signin/core/browser/profile_oauth2_token_service.h"
     30 #include "components/signin/core/browser/signin_manager.h"
     31 #include "content/public/browser/browser_thread.h"
     32 #include "content/public/browser/notification_service.h"
     33 #include "extensions/browser/extension_system.h"
     34 #include "extensions/common/extension.h"
     35 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
     36 #include "extensions/common/manifest_handlers/offline_enabled_info.h"
     37 #include "google_apis/gaia/gaia_auth_consumer.h"
     38 #include "google_apis/gaia/gaia_constants.h"
     39 #include "net/base/load_flags.h"
     40 #include "net/url_request/url_fetcher.h"
     41 #include "net/url_request/url_fetcher_delegate.h"
     42 #include "net/url_request/url_request_context_getter.h"
     43 #include "net/url_request/url_request_status.h"
     44 #include "url/gurl.h"
     45 
     46 using content::BrowserThread;
     47 using extensions::Extension;
     48 using extensions::WebstoreStartupInstaller;
     49 
     50 namespace chromeos {
     51 
     52 namespace {
     53 
     54 const char kOAuthRefreshToken[] = "refresh_token";
     55 const char kOAuthClientId[] = "client_id";
     56 const char kOAuthClientSecret[] = "client_secret";
     57 
     58 const base::FilePath::CharType kOAuthFileName[] =
     59     FILE_PATH_LITERAL("kiosk_auth");
     60 
     61 const int kMaxInstallAttempt = 5;
     62 
     63 }  // namespace
     64 
     65 StartupAppLauncher::StartupAppLauncher(Profile* profile,
     66                                        const std::string& app_id,
     67                                        bool diagnostic_mode,
     68                                        StartupAppLauncher::Delegate* delegate)
     69     : profile_(profile),
     70       app_id_(app_id),
     71       diagnostic_mode_(diagnostic_mode),
     72       delegate_(delegate),
     73       network_ready_handled_(false),
     74       install_attempt_(0),
     75       ready_to_launch_(false) {
     76   DCHECK(profile_);
     77   DCHECK(Extension::IdIsValid(app_id_));
     78 }
     79 
     80 StartupAppLauncher::~StartupAppLauncher() {
     81   // StartupAppLauncher can be deleted at anytime during the launch process
     82   // through a user bailout shortcut.
     83   ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
     84       ->RemoveObserver(this);
     85 }
     86 
     87 void StartupAppLauncher::Initialize() {
     88   StartLoadingOAuthFile();
     89 }
     90 
     91 void StartupAppLauncher::ContinueWithNetworkReady() {
     92   // Starts install if it is not started.
     93   if (!network_ready_handled_) {
     94     network_ready_handled_ = true;
     95     MaybeInstall();
     96   }
     97 }
     98 
     99 void StartupAppLauncher::StartLoadingOAuthFile() {
    100   delegate_->OnLoadingOAuthFile();
    101 
    102   KioskOAuthParams* auth_params = new KioskOAuthParams();
    103   BrowserThread::PostBlockingPoolTaskAndReply(
    104       FROM_HERE,
    105       base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool,
    106                  auth_params),
    107       base::Bind(&StartupAppLauncher::OnOAuthFileLoaded,
    108                  AsWeakPtr(),
    109                  base::Owned(auth_params)));
    110 }
    111 
    112 // static.
    113 void StartupAppLauncher::LoadOAuthFileOnBlockingPool(
    114     KioskOAuthParams* auth_params) {
    115   int error_code = JSONFileValueSerializer::JSON_NO_ERROR;
    116   std::string error_msg;
    117   base::FilePath user_data_dir;
    118   CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
    119   base::FilePath auth_file = user_data_dir.Append(kOAuthFileName);
    120   scoped_ptr<JSONFileValueSerializer> serializer(
    121       new JSONFileValueSerializer(user_data_dir.Append(kOAuthFileName)));
    122   scoped_ptr<base::Value> value(
    123       serializer->Deserialize(&error_code, &error_msg));
    124   base::DictionaryValue* dict = NULL;
    125   if (error_code != JSONFileValueSerializer::JSON_NO_ERROR ||
    126       !value.get() || !value->GetAsDictionary(&dict)) {
    127     LOG(WARNING) << "Can't find auth file at " << auth_file.value();
    128     return;
    129   }
    130 
    131   dict->GetString(kOAuthRefreshToken, &auth_params->refresh_token);
    132   dict->GetString(kOAuthClientId, &auth_params->client_id);
    133   dict->GetString(kOAuthClientSecret, &auth_params->client_secret);
    134 }
    135 
    136 void StartupAppLauncher::OnOAuthFileLoaded(KioskOAuthParams* auth_params) {
    137   auth_params_ = *auth_params;
    138   // Override chrome client_id and secret that will be used for identity
    139   // API token minting.
    140   if (!auth_params_.client_id.empty() && !auth_params_.client_secret.empty()) {
    141     UserManager::Get()->SetAppModeChromeClientOAuthInfo(
    142             auth_params_.client_id,
    143             auth_params_.client_secret);
    144   }
    145 
    146   // If we are restarting chrome (i.e. on crash), we need to initialize
    147   // OAuth2TokenService as well.
    148   InitializeTokenService();
    149 }
    150 
    151 void StartupAppLauncher::RestartLauncher() {
    152   // If the installer is still running in the background, we don't need to
    153   // restart the launch process. We will just wait until it completes and
    154   // lunches the kiosk app.
    155   if (installer_ != NULL) {
    156     LOG(WARNING) << "Installer still running";
    157     return;
    158   }
    159 
    160   MaybeInitializeNetwork();
    161 }
    162 
    163 void StartupAppLauncher::MaybeInitializeNetwork() {
    164   network_ready_handled_ = false;
    165 
    166   const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
    167       extension_service()->GetInstalledExtension(app_id_);
    168   const bool requires_network = !extension ||
    169       !extensions::OfflineEnabledInfo::IsOfflineEnabled(extension);
    170 
    171   if (requires_network) {
    172     delegate_->InitializeNetwork();
    173     return;
    174   }
    175 
    176   // Offline enabled app attempts update if network is ready. Otherwise,
    177   // go directly to launch.
    178   if (delegate_->IsNetworkReady())
    179     ContinueWithNetworkReady();
    180   else
    181     OnReadyToLaunch();
    182 }
    183 
    184 void StartupAppLauncher::InitializeTokenService() {
    185   delegate_->OnInitializingTokenService();
    186 
    187   ProfileOAuth2TokenService* profile_token_service =
    188       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
    189   SigninManagerBase* signin_manager =
    190       SigninManagerFactory::GetForProfile(profile_);
    191   const std::string primary_account_id =
    192       signin_manager->GetAuthenticatedAccountId();
    193   if (profile_token_service->RefreshTokenIsAvailable(primary_account_id) ||
    194       auth_params_.refresh_token.empty()) {
    195     MaybeInitializeNetwork();
    196   } else {
    197     // Pass oauth2 refresh token from the auth file.
    198     // TODO(zelidrag): We should probably remove this option after M27.
    199     // TODO(fgorski): This can go when we have persistence implemented on PO2TS.
    200     // Unless the code is no longer needed.
    201     // TODO(rogerta): Now that this CL implements token persistence in PO2TS, is
    202     // this code still needed?  See above two TODOs.
    203     //
    204     // ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or
    205     // OnRefreshTokensLoaded. Given that we want to handle exactly one event,
    206     // whichever comes first, both handlers call RemoveObserver on PO2TS.
    207     // Handling any of the two events is the only way to resume the execution
    208     // and enable Cleanup method to be called, self-invoking a destructor.
    209     profile_token_service->AddObserver(this);
    210 
    211     profile_token_service->UpdateCredentials(
    212         primary_account_id,
    213         auth_params_.refresh_token);
    214   }
    215 }
    216 
    217 void StartupAppLauncher::OnRefreshTokenAvailable(
    218     const std::string& account_id) {
    219   ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
    220       ->RemoveObserver(this);
    221   MaybeInitializeNetwork();
    222 }
    223 
    224 void StartupAppLauncher::OnRefreshTokensLoaded() {
    225   ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
    226       ->RemoveObserver(this);
    227   MaybeInitializeNetwork();
    228 }
    229 
    230 void StartupAppLauncher::LaunchApp() {
    231   if (!ready_to_launch_) {
    232     NOTREACHED();
    233     LOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
    234   }
    235 
    236   const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
    237       extension_service()->GetInstalledExtension(app_id_);
    238   CHECK(extension);
    239 
    240   if (!extensions::KioskModeInfo::IsKioskEnabled(extension)) {
    241     OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED);
    242     return;
    243   }
    244 
    245   // Always open the app in a window.
    246   OpenApplication(AppLaunchParams(profile_, extension,
    247                                   extensions::LAUNCH_CONTAINER_WINDOW,
    248                                   NEW_WINDOW));
    249   InitAppSession(profile_, app_id_);
    250 
    251   UserManager::Get()->SessionStarted();
    252 
    253   content::NotificationService::current()->Notify(
    254       chrome::NOTIFICATION_KIOSK_APP_LAUNCHED,
    255       content::NotificationService::AllSources(),
    256       content::NotificationService::NoDetails());
    257 
    258   if (diagnostic_mode_)
    259     KioskDiagnosisRunner::Run(profile_, app_id_);
    260 
    261   OnLaunchSuccess();
    262 }
    263 
    264 void StartupAppLauncher::OnLaunchSuccess() {
    265   delegate_->OnLaunchSucceeded();
    266 }
    267 
    268 void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) {
    269   LOG(ERROR) << "App launch failed, error: " << error;
    270   DCHECK_NE(KioskAppLaunchError::NONE, error);
    271 
    272   delegate_->OnLaunchFailed(error);
    273 }
    274 
    275 void StartupAppLauncher::MaybeInstall() {
    276   delegate_->OnInstallingApp();
    277 
    278   ExtensionService* extension_service =
    279       extensions::ExtensionSystem::Get(profile_)->extension_service();
    280   if (!extension_service->GetInstalledExtension(app_id_)) {
    281     BeginInstall();
    282     return;
    283   }
    284 
    285   extensions::ExtensionUpdater::CheckParams check_params;
    286   check_params.ids.push_back(app_id_);
    287   check_params.install_immediately = true;
    288   check_params.callback =
    289       base::Bind(&StartupAppLauncher::OnUpdateCheckFinished, AsWeakPtr());
    290   extension_service->updater()->CheckNow(check_params);
    291 }
    292 
    293 void StartupAppLauncher::OnUpdateCheckFinished() {
    294   OnReadyToLaunch();
    295   UpdateAppData();
    296 }
    297 
    298 void StartupAppLauncher::BeginInstall() {
    299   installer_ = new WebstoreStartupInstaller(
    300       app_id_,
    301       profile_,
    302       false,
    303       base::Bind(&StartupAppLauncher::InstallCallback, AsWeakPtr()));
    304   installer_->BeginInstall();
    305 }
    306 
    307 void StartupAppLauncher::InstallCallback(bool success,
    308                                          const std::string& error) {
    309   installer_ = NULL;
    310   if (delegate_->IsShowingNetworkConfigScreen()) {
    311     LOG(WARNING) << "Showing network config screen";
    312     return;
    313   }
    314 
    315   if (success) {
    316     // Finish initialization after the callback returns.
    317     // So that the app finishes its installation.
    318     BrowserThread::PostTask(
    319         BrowserThread::UI,
    320         FROM_HERE,
    321         base::Bind(&StartupAppLauncher::OnReadyToLaunch,
    322                    AsWeakPtr()));
    323     return;
    324   }
    325 
    326   LOG(ERROR) << "App install failed: " << error
    327              << ", for attempt " << install_attempt_;
    328 
    329   ++install_attempt_;
    330   if (install_attempt_ < kMaxInstallAttempt) {
    331     BrowserThread::PostTask(
    332         BrowserThread::UI,
    333         FROM_HERE,
    334         base::Bind(&StartupAppLauncher::MaybeInitializeNetwork,
    335                    AsWeakPtr()));
    336     return;
    337   }
    338 
    339   OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
    340 }
    341 
    342 void StartupAppLauncher::OnReadyToLaunch() {
    343   ready_to_launch_ = true;
    344   delegate_->OnReadyToLaunch();
    345 }
    346 
    347 void StartupAppLauncher::UpdateAppData() {
    348   KioskAppManager::Get()->ClearAppData(app_id_);
    349   KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_, profile_, NULL);
    350 }
    351 
    352 }   // namespace chromeos
    353