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.
5 #include "chrome/browser/chromeos/app_mode/startup_app_launcher.h"
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/manifest_fetch_data.h"
20 #include "chrome/browser/extensions/updater/safe_manifest_parser.h"
21 #include "chrome/browser/extensions/webstore_startup_installer.h"
22 #include "chrome/browser/lifetime/application_lifetime.h"
23 #include "chrome/browser/signin/profile_oauth2_token_service.h"
24 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
25 #include "chrome/browser/signin/signin_manager.h"
26 #include "chrome/browser/signin/signin_manager_factory.h"
27 #include "chrome/browser/ui/extensions/application_launch.h"
28 #include "chrome/common/chrome_paths.h"
29 #include "chrome/common/chrome_switches.h"
30 #include "chrome/common/chrome_version_info.h"
31 #include "chrome/common/extensions/manifest_url_handler.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/notification_service.h"
34 #include "extensions/common/extension.h"
35 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
36 #include "google_apis/gaia/gaia_auth_consumer.h"
37 #include "google_apis/gaia/gaia_constants.h"
38 #include "net/base/load_flags.h"
39 #include "net/url_request/url_fetcher.h"
40 #include "net/url_request/url_fetcher_delegate.h"
41 #include "net/url_request/url_request_context_getter.h"
42 #include "net/url_request/url_request_status.h"
45 using content::BrowserThread
;
46 using extensions::Extension
;
47 using extensions::WebstoreStartupInstaller
;
53 const char kOAuthRefreshToken
[] = "refresh_token";
54 const char kOAuthClientId
[] = "client_id";
55 const char kOAuthClientSecret
[] = "client_secret";
57 const base::FilePath::CharType kOAuthFileName
[] =
58 FILE_PATH_LITERAL("kiosk_auth");
62 class StartupAppLauncher::AppUpdateChecker
63 : public base::SupportsWeakPtr
<AppUpdateChecker
>,
64 public net::URLFetcherDelegate
{
66 explicit AppUpdateChecker(StartupAppLauncher
* launcher
)
67 : launcher_(launcher
),
68 profile_(launcher
->profile_
),
69 app_id_(launcher
->app_id_
) {}
70 virtual ~AppUpdateChecker() {}
73 const Extension
* app
= GetInstalledApp();
75 launcher_
->OnUpdateCheckNotInstalled();
79 GURL update_url
= extensions::ManifestURL::GetUpdateURL(app
);
80 if (update_url
.is_empty())
81 update_url
= extension_urls::GetWebstoreUpdateUrl();
82 if (!update_url
.is_valid()) {
83 launcher_
->OnUpdateCheckNoUpdate();
87 manifest_fetch_data_
.reset(
88 new extensions::ManifestFetchData(update_url
, 0));
89 manifest_fetch_data_
->AddExtension(
90 app_id_
, app
->version()->GetString(), NULL
, "", "");
92 manifest_fetcher_
.reset(net::URLFetcher::Create(
93 manifest_fetch_data_
->full_url(), net::URLFetcher::GET
, this));
94 manifest_fetcher_
->SetRequestContext(profile_
->GetRequestContext());
95 manifest_fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
96 net::LOAD_DO_NOT_SAVE_COOKIES
|
97 net::LOAD_DISABLE_CACHE
);
98 manifest_fetcher_
->SetAutomaticallyRetryOnNetworkChanges(3);
99 manifest_fetcher_
->Start();
103 const Extension
* GetInstalledApp() {
104 ExtensionService
* extension_service
=
105 extensions::ExtensionSystem::Get(profile_
)->extension_service();
106 return extension_service
->GetInstalledExtension(app_id_
);
109 void HandleManifestResults(const extensions::ManifestFetchData
& fetch_data
,
110 const UpdateManifest::Results
* results
) {
111 if (!results
|| results
->list
.empty()) {
112 launcher_
->OnUpdateCheckNoUpdate();
116 DCHECK_EQ(1u, results
->list
.size());
118 const UpdateManifest::Result
& update
= results
->list
[0];
120 if (update
.browser_min_version
.length() > 0) {
121 Version browser_version
;
122 chrome::VersionInfo version_info
;
123 if (version_info
.is_valid())
124 browser_version
= Version(version_info
.Version());
126 Version
browser_min_version(update
.browser_min_version
);
127 if (browser_version
.IsValid() &&
128 browser_min_version
.IsValid() &&
129 browser_min_version
.CompareTo(browser_version
) > 0) {
130 launcher_
->OnUpdateCheckNoUpdate();
135 const Version
& existing_version
= *GetInstalledApp()->version();
136 Version
update_version(update
.version
);
137 if (existing_version
.IsValid() &&
138 update_version
.IsValid() &&
139 update_version
.CompareTo(existing_version
) <= 0) {
140 launcher_
->OnUpdateCheckNoUpdate();
144 launcher_
->OnUpdateCheckUpdateAvailable();
147 // net::URLFetcherDelegate implementation.
148 virtual void OnURLFetchComplete(const net::URLFetcher
* source
) OVERRIDE
{
149 DCHECK_EQ(source
, manifest_fetcher_
.get());
151 if (source
->GetStatus().status() != net::URLRequestStatus::SUCCESS
||
152 source
->GetResponseCode() != 200) {
153 launcher_
->OnUpdateCheckNoUpdate();
158 source
->GetResponseAsString(&data
);
159 scoped_refptr
<extensions::SafeManifestParser
> safe_parser(
160 new extensions::SafeManifestParser(
162 manifest_fetch_data_
.release(),
163 base::Bind(&AppUpdateChecker::HandleManifestResults
,
165 safe_parser
->Start();
168 StartupAppLauncher
* launcher_
;
170 const std::string app_id_
;
172 scoped_ptr
<extensions::ManifestFetchData
> manifest_fetch_data_
;
173 scoped_ptr
<net::URLFetcher
> manifest_fetcher_
;
175 DISALLOW_COPY_AND_ASSIGN(AppUpdateChecker
);
178 StartupAppLauncher::StartupAppLauncher(Profile
* profile
,
179 const std::string
& app_id
,
180 StartupAppLauncher::Delegate
* delegate
)
184 install_attempted_(false),
185 ready_to_launch_(false) {
187 DCHECK(Extension::IdIsValid(app_id_
));
190 StartupAppLauncher::~StartupAppLauncher() {
191 // StartupAppLauncher can be deleted at anytime during the launch process
192 // through a user bailout shortcut.
193 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_
)
194 ->RemoveObserver(this);
197 void StartupAppLauncher::Initialize() {
198 StartLoadingOAuthFile();
201 void StartupAppLauncher::ContinueWithNetworkReady() {
202 // Starts install if it is not started.
203 if (!install_attempted_
) {
204 install_attempted_
= true;
209 void StartupAppLauncher::StartLoadingOAuthFile() {
210 delegate_
->OnLoadingOAuthFile();
212 KioskOAuthParams
* auth_params
= new KioskOAuthParams();
213 BrowserThread::PostBlockingPoolTaskAndReply(
215 base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool
,
217 base::Bind(&StartupAppLauncher::OnOAuthFileLoaded
,
219 base::Owned(auth_params
)));
223 void StartupAppLauncher::LoadOAuthFileOnBlockingPool(
224 KioskOAuthParams
* auth_params
) {
225 int error_code
= JSONFileValueSerializer::JSON_NO_ERROR
;
226 std::string error_msg
;
227 base::FilePath user_data_dir
;
228 CHECK(PathService::Get(chrome::DIR_USER_DATA
, &user_data_dir
));
229 base::FilePath auth_file
= user_data_dir
.Append(kOAuthFileName
);
230 scoped_ptr
<JSONFileValueSerializer
> serializer(
231 new JSONFileValueSerializer(user_data_dir
.Append(kOAuthFileName
)));
232 scoped_ptr
<base::Value
> value(
233 serializer
->Deserialize(&error_code
, &error_msg
));
234 base::DictionaryValue
* dict
= NULL
;
235 if (error_code
!= JSONFileValueSerializer::JSON_NO_ERROR
||
236 !value
.get() || !value
->GetAsDictionary(&dict
)) {
237 LOG(WARNING
) << "Can't find auth file at " << auth_file
.value();
241 dict
->GetString(kOAuthRefreshToken
, &auth_params
->refresh_token
);
242 dict
->GetString(kOAuthClientId
, &auth_params
->client_id
);
243 dict
->GetString(kOAuthClientSecret
, &auth_params
->client_secret
);
246 void StartupAppLauncher::OnOAuthFileLoaded(KioskOAuthParams
* auth_params
) {
247 auth_params_
= *auth_params
;
248 // Override chrome client_id and secret that will be used for identity
249 // API token minting.
250 if (!auth_params_
.client_id
.empty() && !auth_params_
.client_secret
.empty()) {
251 UserManager::Get()->SetAppModeChromeClientOAuthInfo(
252 auth_params_
.client_id
,
253 auth_params_
.client_secret
);
256 // If we are restarting chrome (i.e. on crash), we need to initialize
257 // OAuth2TokenService as well.
258 InitializeTokenService();
261 void StartupAppLauncher::InitializeNetwork() {
262 delegate_
->InitializeNetwork();
265 void StartupAppLauncher::InitializeTokenService() {
266 delegate_
->OnInitializingTokenService();
268 ProfileOAuth2TokenService
* profile_token_service
=
269 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_
);
270 SigninManagerBase
* signin_manager
=
271 SigninManagerFactory::GetForProfile(profile_
);
272 if (profile_token_service
->RefreshTokenIsAvailable(
273 signin_manager
->GetAuthenticatedAccountId()) ||
274 auth_params_
.refresh_token
.empty()) {
277 // Pass oauth2 refresh token from the auth file.
278 // TODO(zelidrag): We should probably remove this option after M27.
279 // TODO(fgorski): This can go when we have persistence implemented on PO2TS.
280 // Unless the code is no longer needed.
281 // TODO(rogerta): Now that this CL implements token persistence in PO2TS, is
282 // this code still needed? See above two TODOs.
284 // ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or
285 // OnRefreshTokensLoaded. Given that we want to handle exactly one event,
286 // whichever comes first, both handlers call RemoveObserver on PO2TS.
287 // Handling any of the two events is the only way to resume the execution
288 // and enable Cleanup method to be called, self-invoking a destructor.
289 profile_token_service
->AddObserver(this);
291 profile_token_service
->UpdateCredentials(
292 "kiosk_mode@localhost",
293 auth_params_
.refresh_token
);
297 void StartupAppLauncher::OnRefreshTokenAvailable(
298 const std::string
& account_id
) {
299 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_
)
300 ->RemoveObserver(this);
304 void StartupAppLauncher::OnRefreshTokensLoaded() {
305 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_
)
306 ->RemoveObserver(this);
310 void StartupAppLauncher::LaunchApp() {
311 if (!ready_to_launch_
) {
313 LOG(ERROR
) << "LaunchApp() called but launcher is not initialized.";
316 const Extension
* extension
= extensions::ExtensionSystem::Get(profile_
)->
317 extension_service()->GetInstalledExtension(app_id_
);
320 if (!extensions::KioskModeInfo::IsKioskEnabled(extension
)) {
321 OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED
);
325 // Always open the app in a window.
326 OpenApplication(AppLaunchParams(profile_
, extension
,
327 extensions::LAUNCH_CONTAINER_WINDOW
,
329 InitAppSession(profile_
, app_id_
);
331 UserManager::Get()->SessionStarted();
333 content::NotificationService::current()->Notify(
334 chrome::NOTIFICATION_KIOSK_APP_LAUNCHED
,
335 content::NotificationService::AllSources(),
336 content::NotificationService::NoDetails());
341 void StartupAppLauncher::OnLaunchSuccess() {
342 delegate_
->OnLaunchSucceeded();
345 void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error
) {
346 LOG(ERROR
) << "App launch failed, error: " << error
;
347 DCHECK_NE(KioskAppLaunchError::NONE
, error
);
349 delegate_
->OnLaunchFailed(error
);
352 void StartupAppLauncher::MaybeInstall() {
353 delegate_
->OnInstallingApp();
355 update_checker_
.reset(new AppUpdateChecker(this));
356 update_checker_
->Start();
359 void StartupAppLauncher::OnUpdateCheckNotInstalled() {
363 void StartupAppLauncher::OnUpdateCheckUpdateAvailable() {
364 // Uninstall to force a re-install.
365 // TODO(xiyuan): Find a better way. Either download CRX and install it
366 // directly or integrate with ExtensionUpdater in someway.
367 ExtensionService
* extension_service
=
368 extensions::ExtensionSystem::Get(profile_
)->extension_service();
369 extension_service
->UninstallExtension(app_id_
, false, NULL
);
371 OnUpdateCheckNotInstalled();
374 void StartupAppLauncher::OnUpdateCheckNoUpdate() {
378 void StartupAppLauncher::BeginInstall() {
379 installer_
= new WebstoreStartupInstaller(
383 base::Bind(&StartupAppLauncher::InstallCallback
, AsWeakPtr()));
384 installer_
->BeginInstall();
387 void StartupAppLauncher::InstallCallback(bool success
,
388 const std::string
& error
) {
391 // Finish initialization after the callback returns.
392 // So that the app finishes its installation.
393 BrowserThread::PostTask(
396 base::Bind(&StartupAppLauncher::OnReadyToLaunch
,
399 // Schedule app data update after installation.
400 BrowserThread::PostTask(
403 base::Bind(&StartupAppLauncher::UpdateAppData
,
408 LOG(ERROR
) << "App install failed: " << error
;
409 OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL
);
412 void StartupAppLauncher::OnReadyToLaunch() {
413 ready_to_launch_
= true;
414 delegate_
->OnReadyToLaunch();
417 void StartupAppLauncher::UpdateAppData() {
418 KioskAppManager::Get()->ClearAppData(app_id_
);
419 KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_
, profile_
, NULL
);
422 } // namespace chromeos