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
49using content::BrowserThread;
50using extensions::Extension;
51
52namespace chromeos {
53
54namespace {
55
56const char kOAuthRefreshToken[] = "refresh_token";
57const char kOAuthClientId[] = "client_id";
58const char kOAuthClientSecret[] = "client_secret";
59
60const base::FilePath::CharType kOAuthFileName[] =
61    FILE_PATH_LITERAL("kiosk_auth");
62
63const int kMaxLaunchAttempt = 5;
64
65}  // namespace
66
67StartupAppLauncher::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
84StartupAppLauncher::~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
93void StartupAppLauncher::Initialize() {
94  StartLoadingOAuthFile();
95}
96
97void 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
108void 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.
122void 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
145void 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
160void 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
175void 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
199void 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
232void StartupAppLauncher::OnRefreshTokenAvailable(
233    const std::string& account_id) {
234  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
235      ->RemoveObserver(this);
236  MaybeInitializeNetwork();
237}
238
239void StartupAppLauncher::OnRefreshTokensLoaded() {
240  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
241      ->RemoveObserver(this);
242  MaybeInitializeNetwork();
243}
244
245void 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
271void 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
294void StartupAppLauncher::OnKioskExtensionLoadedInCache(
295    const std::string& app_id) {
296  OnKioskAppDataLoadStatusChanged(app_id);
297}
298
299void StartupAppLauncher::OnKioskExtensionDownloadFailed(
300    const std::string& app_id) {
301  OnKioskAppDataLoadStatusChanged(app_id);
302}
303
304void 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
316void 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
350void StartupAppLauncher::OnLaunchSuccess() {
351  delegate_->OnLaunchSucceeded();
352}
353
354void 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
361void StartupAppLauncher::OnUpdateCheckFinished() {
362  OnReadyToLaunch();
363  UpdateAppData();
364}
365
366void 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
391void StartupAppLauncher::OnReadyToLaunch() {
392  ready_to_launch_ = true;
393  delegate_->OnReadyToLaunch();
394}
395
396void StartupAppLauncher::UpdateAppData() {
397  KioskAppManager::Get()->ClearAppData(app_id_);
398  KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_, profile_, NULL);
399}
400
401}   // namespace chromeos
402