startup_app_launcher.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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/webstore_startup_installer.h"
20#include "chrome/browser/lifetime/application_lifetime.h"
21#include "chrome/browser/signin/profile_oauth2_token_service.h"
22#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
23#include "chrome/browser/signin/token_service.h"
24#include "chrome/browser/signin/token_service_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/extensions/extension.h"
29#include "chrome/common/extensions/manifest_handlers/kiosk_mode_info.h"
30#include "chromeos/cryptohome/cryptohome_library.h"
31#include "content/public/browser/browser_thread.h"
32#include "content/public/browser/notification_service.h"
33#include "google_apis/gaia/gaia_auth_consumer.h"
34#include "google_apis/gaia/gaia_constants.h"
35
36using content::BrowserThread;
37using extensions::Extension;
38using extensions::WebstoreStartupInstaller;
39
40namespace chromeos {
41
42namespace {
43
44const char kOAuthRefreshToken[] = "refresh_token";
45const char kOAuthClientId[] = "client_id";
46const char kOAuthClientSecret[] = "client_secret";
47
48const base::FilePath::CharType kOAuthFileName[] =
49    FILE_PATH_LITERAL("kiosk_auth");
50
51bool IsAppInstalled(Profile* profile, const std::string& app_id) {
52  return extensions::ExtensionSystem::Get(profile)->extension_service()->
53      GetInstalledExtension(app_id);
54}
55
56}  // namespace
57
58
59StartupAppLauncher::StartupAppLauncher(Profile* profile,
60                                       const std::string& app_id)
61    : profile_(profile),
62      app_id_(app_id),
63      ready_to_launch_(false) {
64  DCHECK(profile_);
65  DCHECK(Extension::IdIsValid(app_id_));
66}
67
68StartupAppLauncher::~StartupAppLauncher() {
69  // StartupAppLauncher can be deleted at anytime during the launch process
70  // through a user bailout shortcut.
71  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
72      ->RemoveObserver(this);
73  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
74}
75
76void StartupAppLauncher::Initialize() {
77  DVLOG(1) << "Starting... connection = "
78           <<  net::NetworkChangeNotifier::GetConnectionType();
79  StartLoadingOAuthFile();
80}
81
82void StartupAppLauncher::AddObserver(Observer* observer) {
83  observer_list_.AddObserver(observer);
84}
85
86void StartupAppLauncher::RemoveObserver(Observer* observer) {
87  observer_list_.RemoveObserver(observer);
88}
89
90void StartupAppLauncher::StartLoadingOAuthFile() {
91  FOR_EACH_OBSERVER(Observer, observer_list_, 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  // TokenService as well.
139  InitializeTokenService();
140}
141
142void StartupAppLauncher::InitializeNetwork() {
143  FOR_EACH_OBSERVER(Observer, observer_list_, OnInitializingNetwork());
144
145  // TODO(tengs): Use NetworkStateInformer instead because it can handle
146  // portal and proxy detection. We will need to do some refactoring to
147  // make NetworkStateInformer more independent from the WebUI handlers.
148  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
149  OnNetworkChanged(net::NetworkChangeNotifier::GetConnectionType());
150}
151
152void StartupAppLauncher::InitializeTokenService() {
153  FOR_EACH_OBSERVER(Observer, observer_list_, OnInitializingTokenService());
154
155  ProfileOAuth2TokenService* profile_token_service =
156      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
157  if (profile_token_service->RefreshTokenIsAvailable(
158          profile_token_service->GetPrimaryAccountId())) {
159    InitializeNetwork();
160    return;
161  }
162
163  // At the end of this method, the execution will be put on hold until
164  // ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or
165  // OnRefreshTokensLoaded. Given that we want to handle exactly one event,
166  // whichever comes first, both handlers call RemoveObserver on PO2TS. Handling
167  // any of the two events is the only way to resume the execution and enable
168  // Cleanup method to be called, self-invoking a destructor. In destructor
169  // StartupAppLauncher is no longer an observer of PO2TS and there is no need
170  // to call RemoveObserver again.
171  profile_token_service->AddObserver(this);
172
173  TokenService* token_service = TokenServiceFactory::GetForProfile(profile_);
174  token_service->Initialize(GaiaConstants::kChromeSource, profile_);
175
176  // Pass oauth2 refresh token from the auth file.
177  // TODO(zelidrag): We should probably remove this option after M27.
178  // TODO(fgorski): This can go when we have persistence implemented on PO2TS.
179  // Unless the code is no longer needed.
180  if (!auth_params_.refresh_token.empty()) {
181    token_service->UpdateCredentialsWithOAuth2(
182        GaiaAuthConsumer::ClientOAuthResult(
183            auth_params_.refresh_token,
184            std::string(),  // access_token
185            0));            // new_expires_in_secs
186  } else {
187    // Load whatever tokens we have stored there last time around.
188    token_service->LoadTokensFromDB();
189  }
190}
191
192void StartupAppLauncher::OnRefreshTokenAvailable(
193    const std::string& account_id) {
194  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
195      ->RemoveObserver(this);
196  InitializeNetwork();
197}
198
199void StartupAppLauncher::OnRefreshTokensLoaded() {
200  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
201      ->RemoveObserver(this);
202  InitializeNetwork();
203}
204
205void StartupAppLauncher::OnLaunchSuccess() {
206  FOR_EACH_OBSERVER(Observer, observer_list_, OnLaunchSucceeded());
207}
208
209void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) {
210  LOG(ERROR) << "App launch failed, error: " << error;
211  DCHECK_NE(KioskAppLaunchError::NONE, error);
212
213  FOR_EACH_OBSERVER(Observer, observer_list_, OnLaunchFailed(error));
214}
215
216void StartupAppLauncher::LaunchApp() {
217  if (!ready_to_launch_) {
218    NOTREACHED();
219    LOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
220  }
221
222  const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
223      extension_service()->GetInstalledExtension(app_id_);
224  CHECK(extension);
225
226  if (!extensions::KioskModeInfo::IsKioskEnabled(extension)) {
227    OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED);
228    return;
229  }
230
231  // Always open the app in a window.
232  OpenApplication(AppLaunchParams(profile_, extension,
233                                  extension_misc::LAUNCH_WINDOW, NEW_WINDOW));
234  InitAppSession(profile_, app_id_);
235
236  UserManager::Get()->SessionStarted();
237
238  content::NotificationService::current()->Notify(
239      chrome::NOTIFICATION_KIOSK_APP_LAUNCHED,
240      content::NotificationService::AllSources(),
241      content::NotificationService::NoDetails());
242
243  OnLaunchSuccess();
244}
245
246void StartupAppLauncher::BeginInstall() {
247  FOR_EACH_OBSERVER(Observer, observer_list_, OnInstallingApp());
248
249  DVLOG(1) << "BeginInstall... connection = "
250           <<  net::NetworkChangeNotifier::GetConnectionType();
251
252  if (IsAppInstalled(profile_, app_id_)) {
253    OnReadyToLaunch();
254    return;
255  }
256
257  installer_ = new WebstoreStartupInstaller(
258      app_id_,
259      profile_,
260      false,
261      base::Bind(&StartupAppLauncher::InstallCallback, AsWeakPtr()));
262  installer_->BeginInstall();
263}
264
265void StartupAppLauncher::InstallCallback(bool success,
266                                         const std::string& error) {
267  installer_ = NULL;
268  if (success) {
269    // Finish initialization after the callback returns.
270    // So that the app finishes its installation.
271    BrowserThread::PostTask(
272        BrowserThread::UI,
273        FROM_HERE,
274        base::Bind(&StartupAppLauncher::OnReadyToLaunch,
275                   AsWeakPtr()));
276    return;
277  }
278
279  LOG(ERROR) << "App install failed: " << error;
280  OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
281}
282
283void StartupAppLauncher::OnReadyToLaunch() {
284  // Defer app launch until system salt is loaded to make sure that identity
285  // api works with the enterprise kiosk app.
286  // TODO(xiyuan): Use async GetSystemSalt after merging to M31.
287  const std::string system_salt = CryptohomeLibrary::Get()->GetSystemSaltSync();
288  if (system_salt.empty()) {
289    const int64 kRequestSystemSaltDelayMs = 500;
290    BrowserThread::PostDelayedTask(
291        BrowserThread::UI,
292        FROM_HERE,
293        base::Bind(&StartupAppLauncher::OnReadyToLaunch, AsWeakPtr()),
294        base::TimeDelta::FromMilliseconds(kRequestSystemSaltDelayMs));
295    return;
296  }
297
298  ready_to_launch_ = true;
299  FOR_EACH_OBSERVER(Observer, observer_list_, OnReadyToLaunch());
300}
301
302void StartupAppLauncher::OnNetworkChanged(
303    net::NetworkChangeNotifier::ConnectionType type) {
304  DVLOG(1) << "OnNetworkChanged... connection = "
305           <<  net::NetworkChangeNotifier::GetConnectionType();
306  if (!net::NetworkChangeNotifier::IsOffline()) {
307    DVLOG(1) << "Network up and running!";
308    net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
309
310    BeginInstall();
311  } else {
312    DVLOG(1) << "Network not running yet!";
313  }
314}
315
316}   // namespace chromeos
317