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