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/login/user_manager.h"
17#include "chrome/browser/extensions/extension_service.h"
18#include "chrome/browser/extensions/extension_system.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.h"
23#include "chrome/browser/signin/profile_oauth2_token_service_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 "content/public/browser/browser_thread.h"
30#include "content/public/browser/notification_service.h"
31#include "extensions/common/extension.h"
32#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
33#include "google_apis/gaia/gaia_auth_consumer.h"
34#include "google_apis/gaia/gaia_constants.h"
35#include "net/base/load_flags.h"
36#include "net/url_request/url_fetcher.h"
37#include "net/url_request/url_fetcher_delegate.h"
38#include "net/url_request/url_request_context_getter.h"
39#include "net/url_request/url_request_status.h"
40#include "url/gurl.h"
41
42using content::BrowserThread;
43using extensions::Extension;
44using extensions::WebstoreStartupInstaller;
45
46namespace chromeos {
47
48namespace {
49
50const char kOAuthRefreshToken[] = "refresh_token";
51const char kOAuthClientId[] = "client_id";
52const char kOAuthClientSecret[] = "client_secret";
53
54const base::FilePath::CharType kOAuthFileName[] =
55    FILE_PATH_LITERAL("kiosk_auth");
56
57}  // namespace
58
59StartupAppLauncher::StartupAppLauncher(Profile* profile,
60                                       const std::string& app_id,
61                                       StartupAppLauncher::Delegate* delegate)
62    : profile_(profile),
63      app_id_(app_id),
64      delegate_(delegate),
65      install_attempted_(false),
66      ready_to_launch_(false) {
67  DCHECK(profile_);
68  DCHECK(Extension::IdIsValid(app_id_));
69}
70
71StartupAppLauncher::~StartupAppLauncher() {
72  // StartupAppLauncher can be deleted at anytime during the launch process
73  // through a user bailout shortcut.
74  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
75      ->RemoveObserver(this);
76}
77
78void StartupAppLauncher::Initialize() {
79  StartLoadingOAuthFile();
80}
81
82void StartupAppLauncher::ContinueWithNetworkReady() {
83  // Starts install if it is not started.
84  if (!install_attempted_) {
85    install_attempted_ = true;
86    MaybeInstall();
87  }
88}
89
90void StartupAppLauncher::StartLoadingOAuthFile() {
91  delegate_->OnLoadingOAuthFile();
92
93  KioskOAuthParams* auth_params = new KioskOAuthParams();
94  BrowserThread::PostBlockingPoolTaskAndReply(
95      FROM_HERE,
96      base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool,
97                 auth_params),
98      base::Bind(&StartupAppLauncher::OnOAuthFileLoaded,
99                 AsWeakPtr(),
100                 base::Owned(auth_params)));
101}
102
103// static.
104void StartupAppLauncher::LoadOAuthFileOnBlockingPool(
105    KioskOAuthParams* auth_params) {
106  int error_code = JSONFileValueSerializer::JSON_NO_ERROR;
107  std::string error_msg;
108  base::FilePath user_data_dir;
109  CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
110  base::FilePath auth_file = user_data_dir.Append(kOAuthFileName);
111  scoped_ptr<JSONFileValueSerializer> serializer(
112      new JSONFileValueSerializer(user_data_dir.Append(kOAuthFileName)));
113  scoped_ptr<base::Value> value(
114      serializer->Deserialize(&error_code, &error_msg));
115  base::DictionaryValue* dict = NULL;
116  if (error_code != JSONFileValueSerializer::JSON_NO_ERROR ||
117      !value.get() || !value->GetAsDictionary(&dict)) {
118    LOG(WARNING) << "Can't find auth file at " << auth_file.value();
119    return;
120  }
121
122  dict->GetString(kOAuthRefreshToken, &auth_params->refresh_token);
123  dict->GetString(kOAuthClientId, &auth_params->client_id);
124  dict->GetString(kOAuthClientSecret, &auth_params->client_secret);
125}
126
127void StartupAppLauncher::OnOAuthFileLoaded(KioskOAuthParams* auth_params) {
128  auth_params_ = *auth_params;
129  // Override chrome client_id and secret that will be used for identity
130  // API token minting.
131  if (!auth_params_.client_id.empty() && !auth_params_.client_secret.empty()) {
132    UserManager::Get()->SetAppModeChromeClientOAuthInfo(
133            auth_params_.client_id,
134            auth_params_.client_secret);
135  }
136
137  // If we are restarting chrome (i.e. on crash), we need to initialize
138  // OAuth2TokenService as well.
139  InitializeTokenService();
140}
141
142void StartupAppLauncher::InitializeNetwork() {
143  delegate_->InitializeNetwork();
144}
145
146void StartupAppLauncher::InitializeTokenService() {
147  delegate_->OnInitializingTokenService();
148
149  ProfileOAuth2TokenService* profile_token_service =
150      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
151  if (profile_token_service->RefreshTokenIsAvailable(
152          profile_token_service->GetPrimaryAccountId()) ||
153      auth_params_.refresh_token.empty()) {
154    InitializeNetwork();
155  } else {
156    // Pass oauth2 refresh token from the auth file.
157    // TODO(zelidrag): We should probably remove this option after M27.
158    // TODO(fgorski): This can go when we have persistence implemented on PO2TS.
159    // Unless the code is no longer needed.
160    // TODO(rogerta): Now that this CL implements token persistence in PO2TS, is
161    // this code still needed?  See above two TODOs.
162    //
163    // ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or
164    // OnRefreshTokensLoaded. Given that we want to handle exactly one event,
165    // whichever comes first, both handlers call RemoveObserver on PO2TS.
166    // Handling any of the two events is the only way to resume the execution
167    // and enable Cleanup method to be called, self-invoking a destructor.
168    profile_token_service->AddObserver(this);
169
170    profile_token_service->UpdateCredentials(
171        "kiosk_mode@localhost",
172        auth_params_.refresh_token);
173  }
174}
175
176void StartupAppLauncher::OnRefreshTokenAvailable(
177    const std::string& account_id) {
178  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
179      ->RemoveObserver(this);
180  InitializeNetwork();
181}
182
183void StartupAppLauncher::OnRefreshTokensLoaded() {
184  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
185      ->RemoveObserver(this);
186  InitializeNetwork();
187}
188
189void StartupAppLauncher::LaunchApp() {
190  if (!ready_to_launch_) {
191    NOTREACHED();
192    LOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
193  }
194
195  const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
196      extension_service()->GetInstalledExtension(app_id_);
197  CHECK(extension);
198
199  if (!extensions::KioskModeInfo::IsKioskEnabled(extension)) {
200    OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED);
201    return;
202  }
203
204  // Always open the app in a window.
205  OpenApplication(AppLaunchParams(profile_, extension,
206                                  extensions::LAUNCH_CONTAINER_WINDOW,
207                                  NEW_WINDOW));
208  InitAppSession(profile_, app_id_);
209
210  UserManager::Get()->SessionStarted();
211
212  content::NotificationService::current()->Notify(
213      chrome::NOTIFICATION_KIOSK_APP_LAUNCHED,
214      content::NotificationService::AllSources(),
215      content::NotificationService::NoDetails());
216
217  OnLaunchSuccess();
218}
219
220void StartupAppLauncher::OnLaunchSuccess() {
221  delegate_->OnLaunchSucceeded();
222}
223
224void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) {
225  LOG(ERROR) << "App launch failed, error: " << error;
226  DCHECK_NE(KioskAppLaunchError::NONE, error);
227
228  delegate_->OnLaunchFailed(error);
229}
230
231void StartupAppLauncher::MaybeInstall() {
232  delegate_->OnInstallingApp();
233
234  ExtensionService* extension_service =
235      extensions::ExtensionSystem::Get(profile_)->extension_service();
236  if (!extension_service->GetInstalledExtension(app_id_)) {
237    BeginInstall();
238    return;
239  }
240
241  extensions::ExtensionUpdater::CheckParams check_params;
242  check_params.ids.push_back(app_id_);
243  check_params.install_immediately = true;
244  check_params.callback =
245      base::Bind(&StartupAppLauncher::OnUpdateCheckFinished, AsWeakPtr());
246  extension_service->updater()->CheckNow(check_params);
247}
248
249void StartupAppLauncher::OnUpdateCheckFinished() {
250  OnReadyToLaunch();
251  UpdateAppData();
252}
253
254void StartupAppLauncher::BeginInstall() {
255  installer_ = new WebstoreStartupInstaller(
256      app_id_,
257      profile_,
258      false,
259      base::Bind(&StartupAppLauncher::InstallCallback, AsWeakPtr()));
260  installer_->BeginInstall();
261}
262
263void StartupAppLauncher::InstallCallback(bool success,
264                                         const std::string& error) {
265  installer_ = NULL;
266  if (success) {
267    // Finish initialization after the callback returns.
268    // So that the app finishes its installation.
269    BrowserThread::PostTask(
270        BrowserThread::UI,
271        FROM_HERE,
272        base::Bind(&StartupAppLauncher::OnReadyToLaunch,
273                   AsWeakPtr()));
274    return;
275  }
276
277  LOG(ERROR) << "App install failed: " << error;
278  OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
279}
280
281void StartupAppLauncher::OnReadyToLaunch() {
282  ready_to_launch_ = true;
283  delegate_->OnReadyToLaunch();
284}
285
286void StartupAppLauncher::UpdateAppData() {
287  KioskAppManager::Get()->ClearAppData(app_id_);
288  KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_, profile_, NULL);
289}
290
291}   // namespace chromeos
292