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