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/session/user_session_manager.h"
     18 #include "chrome/browser/extensions/extension_service.h"
     19 #include "chrome/browser/extensions/install_tracker.h"
     20 #include "chrome/browser/extensions/install_tracker_factory.h"
     21 #include "chrome/browser/extensions/updater/extension_updater.h"
     22 #include "chrome/browser/lifetime/application_lifetime.h"
     23 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
     24 #include "chrome/browser/signin/signin_manager_factory.h"
     25 #include "chrome/browser/ui/extensions/application_launch.h"
     26 #include "chrome/common/chrome_paths.h"
     27 #include "chrome/common/chrome_switches.h"
     28 #include "chrome/common/chrome_version_info.h"
     29 #include "chrome/common/extensions/manifest_url_handler.h"
     30 #include "components/crx_file/id_util.h"
     31 #include "components/signin/core/browser/profile_oauth2_token_service.h"
     32 #include "components/signin/core/browser/signin_manager.h"
     33 #include "components/user_manager/user_manager.h"
     34 #include "content/public/browser/browser_thread.h"
     35 #include "content/public/browser/notification_service.h"
     36 #include "extensions/browser/extension_system.h"
     37 #include "extensions/common/extension.h"
     38 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
     39 #include "extensions/common/manifest_handlers/offline_enabled_info.h"
     40 #include "google_apis/gaia/gaia_auth_consumer.h"
     41 #include "google_apis/gaia/gaia_constants.h"
     42 #include "net/base/load_flags.h"
     43 #include "net/url_request/url_fetcher.h"
     44 #include "net/url_request/url_fetcher_delegate.h"
     45 #include "net/url_request/url_request_context_getter.h"
     46 #include "net/url_request/url_request_status.h"
     47 #include "url/gurl.h"
     48 
     49 using content::BrowserThread;
     50 using extensions::Extension;
     51 
     52 namespace chromeos {
     53 
     54 namespace {
     55 
     56 const char kOAuthRefreshToken[] = "refresh_token";
     57 const char kOAuthClientId[] = "client_id";
     58 const char kOAuthClientSecret[] = "client_secret";
     59 
     60 const base::FilePath::CharType kOAuthFileName[] =
     61     FILE_PATH_LITERAL("kiosk_auth");
     62 
     63 const int kMaxLaunchAttempt = 5;
     64 
     65 }  // namespace
     66 
     67 StartupAppLauncher::StartupAppLauncher(Profile* profile,
     68                                        const std::string& app_id,
     69                                        bool diagnostic_mode,
     70                                        StartupAppLauncher::Delegate* delegate)
     71     : profile_(profile),
     72       app_id_(app_id),
     73       diagnostic_mode_(diagnostic_mode),
     74       delegate_(delegate),
     75       network_ready_handled_(false),
     76       launch_attempt_(0),
     77       ready_to_launch_(false),
     78       wait_for_crx_update_(false) {
     79   DCHECK(profile_);
     80   DCHECK(crx_file::id_util::IdIsValid(app_id_));
     81   KioskAppManager::Get()->AddObserver(this);
     82 }
     83 
     84 StartupAppLauncher::~StartupAppLauncher() {
     85   KioskAppManager::Get()->RemoveObserver(this);
     86 
     87   // StartupAppLauncher can be deleted at anytime during the launch process
     88   // through a user bailout shortcut.
     89   ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
     90       ->RemoveObserver(this);
     91 }
     92 
     93 void StartupAppLauncher::Initialize() {
     94   StartLoadingOAuthFile();
     95 }
     96 
     97 void StartupAppLauncher::ContinueWithNetworkReady() {
     98   if (!network_ready_handled_) {
     99     network_ready_handled_ = true;
    100     // The network might not be ready when KioskAppManager tries to update
    101     // external cache initially. Update the external cache now that the network
    102     // is ready for sure.
    103     wait_for_crx_update_ = true;
    104     KioskAppManager::Get()->UpdateExternalCache();
    105   }
    106 }
    107 
    108 void StartupAppLauncher::StartLoadingOAuthFile() {
    109   delegate_->OnLoadingOAuthFile();
    110 
    111   KioskOAuthParams* auth_params = new KioskOAuthParams();
    112   BrowserThread::PostBlockingPoolTaskAndReply(
    113       FROM_HERE,
    114       base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool,
    115                  auth_params),
    116       base::Bind(&StartupAppLauncher::OnOAuthFileLoaded,
    117                  AsWeakPtr(),
    118                  base::Owned(auth_params)));
    119 }
    120 
    121 // static.
    122 void StartupAppLauncher::LoadOAuthFileOnBlockingPool(
    123     KioskOAuthParams* auth_params) {
    124   int error_code = JSONFileValueSerializer::JSON_NO_ERROR;
    125   std::string error_msg;
    126   base::FilePath user_data_dir;
    127   CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
    128   base::FilePath auth_file = user_data_dir.Append(kOAuthFileName);
    129   scoped_ptr<JSONFileValueSerializer> serializer(
    130       new JSONFileValueSerializer(user_data_dir.Append(kOAuthFileName)));
    131   scoped_ptr<base::Value> value(
    132       serializer->Deserialize(&error_code, &error_msg));
    133   base::DictionaryValue* dict = NULL;
    134   if (error_code != JSONFileValueSerializer::JSON_NO_ERROR ||
    135       !value.get() || !value->GetAsDictionary(&dict)) {
    136     LOG(WARNING) << "Can't find auth file at " << auth_file.value();
    137     return;
    138   }
    139 
    140   dict->GetString(kOAuthRefreshToken, &auth_params->refresh_token);
    141   dict->GetString(kOAuthClientId, &auth_params->client_id);
    142   dict->GetString(kOAuthClientSecret, &auth_params->client_secret);
    143 }
    144 
    145 void StartupAppLauncher::OnOAuthFileLoaded(KioskOAuthParams* auth_params) {
    146   auth_params_ = *auth_params;
    147   // Override chrome client_id and secret that will be used for identity
    148   // API token minting.
    149   if (!auth_params_.client_id.empty() && !auth_params_.client_secret.empty()) {
    150     UserSessionManager::GetInstance()->SetAppModeChromeClientOAuthInfo(
    151             auth_params_.client_id,
    152             auth_params_.client_secret);
    153   }
    154 
    155   // If we are restarting chrome (i.e. on crash), we need to initialize
    156   // OAuth2TokenService as well.
    157   InitializeTokenService();
    158 }
    159 
    160 void StartupAppLauncher::RestartLauncher() {
    161   // If the installer is still running in the background, we don't need to
    162   // restart the launch process. We will just wait until it completes and
    163   // launches the kiosk app.
    164   if (extensions::ExtensionSystem::Get(profile_)
    165           ->extension_service()
    166           ->pending_extension_manager()
    167           ->IsIdPending(app_id_)) {
    168     LOG(WARNING) << "Installer still running";
    169     return;
    170   }
    171 
    172   MaybeInitializeNetwork();
    173 }
    174 
    175 void StartupAppLauncher::MaybeInitializeNetwork() {
    176   network_ready_handled_ = false;
    177 
    178   const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
    179       extension_service()->GetInstalledExtension(app_id_);
    180   bool crx_cached = KioskAppManager::Get()->HasCachedCrx(app_id_);
    181   const bool requires_network =
    182       (!extension && !crx_cached) ||
    183       (extension &&
    184        !extensions::OfflineEnabledInfo::IsOfflineEnabled(extension));
    185 
    186   if (requires_network) {
    187     delegate_->InitializeNetwork();
    188     return;
    189   }
    190 
    191   // Update the offline enabled crx cache if the network is ready;
    192   // or just install the app.
    193   if (delegate_->IsNetworkReady())
    194     ContinueWithNetworkReady();
    195   else
    196     BeginInstall();
    197 }
    198 
    199 void StartupAppLauncher::InitializeTokenService() {
    200   delegate_->OnInitializingTokenService();
    201 
    202   ProfileOAuth2TokenService* profile_token_service =
    203       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
    204   SigninManagerBase* signin_manager =
    205       SigninManagerFactory::GetForProfile(profile_);
    206   const std::string primary_account_id =
    207       signin_manager->GetAuthenticatedAccountId();
    208   if (profile_token_service->RefreshTokenIsAvailable(primary_account_id) ||
    209       auth_params_.refresh_token.empty()) {
    210     MaybeInitializeNetwork();
    211   } else {
    212     // Pass oauth2 refresh token from the auth file.
    213     // TODO(zelidrag): We should probably remove this option after M27.
    214     // TODO(fgorski): This can go when we have persistence implemented on PO2TS.
    215     // Unless the code is no longer needed.
    216     // TODO(rogerta): Now that this CL implements token persistence in PO2TS, is
    217     // this code still needed?  See above two TODOs.
    218     //
    219     // ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or
    220     // OnRefreshTokensLoaded. Given that we want to handle exactly one event,
    221     // whichever comes first, both handlers call RemoveObserver on PO2TS.
    222     // Handling any of the two events is the only way to resume the execution
    223     // and enable Cleanup method to be called, self-invoking a destructor.
    224     profile_token_service->AddObserver(this);
    225 
    226     profile_token_service->UpdateCredentials(
    227         primary_account_id,
    228         auth_params_.refresh_token);
    229   }
    230 }
    231 
    232 void StartupAppLauncher::OnRefreshTokenAvailable(
    233     const std::string& account_id) {
    234   ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
    235       ->RemoveObserver(this);
    236   MaybeInitializeNetwork();
    237 }
    238 
    239 void StartupAppLauncher::OnRefreshTokensLoaded() {
    240   ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
    241       ->RemoveObserver(this);
    242   MaybeInitializeNetwork();
    243 }
    244 
    245 void StartupAppLauncher::MaybeLaunchApp() {
    246   // Check if the app is offline enabled.
    247   const Extension* extension = extensions::ExtensionSystem::Get(profile_)
    248                                    ->extension_service()
    249                                    ->GetInstalledExtension(app_id_);
    250   DCHECK(extension);
    251   const bool offline_enabled =
    252       extensions::OfflineEnabledInfo::IsOfflineEnabled(extension);
    253   if (offline_enabled || delegate_->IsNetworkReady()) {
    254     BrowserThread::PostTask(
    255         BrowserThread::UI,
    256         FROM_HERE,
    257         base::Bind(&StartupAppLauncher::OnReadyToLaunch, AsWeakPtr()));
    258   } else {
    259     ++launch_attempt_;
    260     if (launch_attempt_ < kMaxLaunchAttempt) {
    261       BrowserThread::PostTask(
    262           BrowserThread::UI,
    263           FROM_HERE,
    264           base::Bind(&StartupAppLauncher::MaybeInitializeNetwork, AsWeakPtr()));
    265       return;
    266     }
    267     OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_LAUNCH);
    268   }
    269 }
    270 
    271 void StartupAppLauncher::OnFinishCrxInstall(const std::string& extension_id,
    272                                             bool success) {
    273   if (extension_id != app_id_)
    274     return;
    275 
    276   extensions::InstallTracker* tracker =
    277       extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
    278   tracker->RemoveObserver(this);
    279   if (delegate_->IsShowingNetworkConfigScreen()) {
    280     LOG(WARNING) << "Showing network config screen";
    281     return;
    282   }
    283 
    284   if (success) {
    285     MaybeLaunchApp();
    286     return;
    287   }
    288 
    289   LOG(ERROR) << "Failed to install the kiosk application app_id: "
    290              << extension_id;
    291   OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
    292 }
    293 
    294 void StartupAppLauncher::OnKioskExtensionLoadedInCache(
    295     const std::string& app_id) {
    296   OnKioskAppDataLoadStatusChanged(app_id);
    297 }
    298 
    299 void StartupAppLauncher::OnKioskExtensionDownloadFailed(
    300     const std::string& app_id) {
    301   OnKioskAppDataLoadStatusChanged(app_id);
    302 }
    303 
    304 void StartupAppLauncher::OnKioskAppDataLoadStatusChanged(
    305     const std::string& app_id) {
    306   if (app_id != app_id_ || !wait_for_crx_update_)
    307     return;
    308 
    309   wait_for_crx_update_ = false;
    310   if (KioskAppManager::Get()->HasCachedCrx(app_id_))
    311     BeginInstall();
    312   else
    313     OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_DOWNLOAD);
    314 }
    315 
    316 void StartupAppLauncher::LaunchApp() {
    317   if (!ready_to_launch_) {
    318     NOTREACHED();
    319     LOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
    320   }
    321 
    322   const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
    323       extension_service()->GetInstalledExtension(app_id_);
    324   CHECK(extension);
    325 
    326   if (!extensions::KioskModeInfo::IsKioskEnabled(extension)) {
    327     OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED);
    328     return;
    329   }
    330 
    331   // Always open the app in a window.
    332   OpenApplication(AppLaunchParams(profile_, extension,
    333                                   extensions::LAUNCH_CONTAINER_WINDOW,
    334                                   NEW_WINDOW));
    335   InitAppSession(profile_, app_id_);
    336 
    337   user_manager::UserManager::Get()->SessionStarted();
    338 
    339   content::NotificationService::current()->Notify(
    340       chrome::NOTIFICATION_KIOSK_APP_LAUNCHED,
    341       content::NotificationService::AllSources(),
    342       content::NotificationService::NoDetails());
    343 
    344   if (diagnostic_mode_)
    345     KioskDiagnosisRunner::Run(profile_, app_id_);
    346 
    347   OnLaunchSuccess();
    348 }
    349 
    350 void StartupAppLauncher::OnLaunchSuccess() {
    351   delegate_->OnLaunchSucceeded();
    352 }
    353 
    354 void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) {
    355   LOG(ERROR) << "App launch failed, error: " << error;
    356   DCHECK_NE(KioskAppLaunchError::NONE, error);
    357 
    358   delegate_->OnLaunchFailed(error);
    359 }
    360 
    361 void StartupAppLauncher::OnUpdateCheckFinished() {
    362   OnReadyToLaunch();
    363   UpdateAppData();
    364 }
    365 
    366 void StartupAppLauncher::BeginInstall() {
    367   KioskAppManager::Get()->InstallFromCache(app_id_);
    368   if (extensions::ExtensionSystem::Get(profile_)
    369           ->extension_service()
    370           ->pending_extension_manager()
    371           ->IsIdPending(app_id_)) {
    372     delegate_->OnInstallingApp();
    373     // Observe the crx installation events.
    374     extensions::InstallTracker* tracker =
    375         extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
    376     tracker->AddObserver(this);
    377     return;
    378   }
    379 
    380   if (extensions::ExtensionSystem::Get(profile_)
    381           ->extension_service()
    382           ->GetInstalledExtension(app_id_)) {
    383     // Launch the app.
    384     OnReadyToLaunch();
    385   } else {
    386     // The extension is skipped for installation due to some error.
    387     OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
    388   }
    389 }
    390 
    391 void StartupAppLauncher::OnReadyToLaunch() {
    392   ready_to_launch_ = true;
    393   delegate_->OnReadyToLaunch();
    394 }
    395 
    396 void StartupAppLauncher::UpdateAppData() {
    397   KioskAppManager::Get()->ClearAppData(app_id_);
    398   KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_, profile_, NULL);
    399 }
    400 
    401 }   // namespace chromeos
    402