Upstream version 5.34.92.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / app_mode / startup_app_launcher.cc
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/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"
43 #include "url/gurl.h"
44
45 using content::BrowserThread;
46 using extensions::Extension;
47 using extensions::WebstoreStartupInstaller;
48
49 namespace chromeos {
50
51 namespace {
52
53 const char kOAuthRefreshToken[] = "refresh_token";
54 const char kOAuthClientId[] = "client_id";
55 const char kOAuthClientSecret[] = "client_secret";
56
57 const base::FilePath::CharType kOAuthFileName[] =
58     FILE_PATH_LITERAL("kiosk_auth");
59
60 }  // namespace
61
62 class StartupAppLauncher::AppUpdateChecker
63     : public base::SupportsWeakPtr<AppUpdateChecker>,
64       public net::URLFetcherDelegate {
65  public:
66   explicit AppUpdateChecker(StartupAppLauncher* launcher)
67       : launcher_(launcher),
68         profile_(launcher->profile_),
69         app_id_(launcher->app_id_) {}
70   virtual ~AppUpdateChecker() {}
71
72   void Start() {
73     const Extension* app = GetInstalledApp();
74     if (!app) {
75       launcher_->OnUpdateCheckNotInstalled();
76       return;
77     }
78
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();
84       return;
85     }
86
87     manifest_fetch_data_.reset(
88         new extensions::ManifestFetchData(update_url, 0));
89     manifest_fetch_data_->AddExtension(
90         app_id_, app->version()->GetString(), NULL, "", "");
91
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();
100   }
101
102  private:
103   const Extension* GetInstalledApp() {
104     ExtensionService* extension_service =
105         extensions::ExtensionSystem::Get(profile_)->extension_service();
106     return extension_service->GetInstalledExtension(app_id_);
107   }
108
109   void HandleManifestResults(const extensions::ManifestFetchData& fetch_data,
110                              const UpdateManifest::Results* results) {
111     if (!results || results->list.empty()) {
112       launcher_->OnUpdateCheckNoUpdate();
113       return;
114     }
115
116     DCHECK_EQ(1u, results->list.size());
117
118     const UpdateManifest::Result& update = results->list[0];
119
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());
125
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();
131         return;
132       }
133     }
134
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();
141       return;
142     }
143
144     launcher_->OnUpdateCheckUpdateAvailable();
145   }
146
147   // net::URLFetcherDelegate implementation.
148   virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
149     DCHECK_EQ(source, manifest_fetcher_.get());
150
151     if (source->GetStatus().status() != net::URLRequestStatus::SUCCESS ||
152         source->GetResponseCode() != 200) {
153       launcher_->OnUpdateCheckNoUpdate();
154       return;
155     }
156
157     std::string data;
158     source->GetResponseAsString(&data);
159     scoped_refptr<extensions::SafeManifestParser> safe_parser(
160         new extensions::SafeManifestParser(
161             data,
162             manifest_fetch_data_.release(),
163             base::Bind(&AppUpdateChecker::HandleManifestResults,
164                        AsWeakPtr())));
165     safe_parser->Start();
166   }
167
168   StartupAppLauncher* launcher_;
169   Profile* profile_;
170   const std::string app_id_;
171
172   scoped_ptr<extensions::ManifestFetchData> manifest_fetch_data_;
173   scoped_ptr<net::URLFetcher> manifest_fetcher_;
174
175   DISALLOW_COPY_AND_ASSIGN(AppUpdateChecker);
176 };
177
178 StartupAppLauncher::StartupAppLauncher(Profile* profile,
179                                        const std::string& app_id,
180                                        StartupAppLauncher::Delegate* delegate)
181     : profile_(profile),
182       app_id_(app_id),
183       delegate_(delegate),
184       install_attempted_(false),
185       ready_to_launch_(false) {
186   DCHECK(profile_);
187   DCHECK(Extension::IdIsValid(app_id_));
188 }
189
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);
195 }
196
197 void StartupAppLauncher::Initialize() {
198   StartLoadingOAuthFile();
199 }
200
201 void StartupAppLauncher::ContinueWithNetworkReady() {
202   // Starts install if it is not started.
203   if (!install_attempted_) {
204     install_attempted_ = true;
205     MaybeInstall();
206   }
207 }
208
209 void StartupAppLauncher::StartLoadingOAuthFile() {
210   delegate_->OnLoadingOAuthFile();
211
212   KioskOAuthParams* auth_params = new KioskOAuthParams();
213   BrowserThread::PostBlockingPoolTaskAndReply(
214       FROM_HERE,
215       base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool,
216                  auth_params),
217       base::Bind(&StartupAppLauncher::OnOAuthFileLoaded,
218                  AsWeakPtr(),
219                  base::Owned(auth_params)));
220 }
221
222 // static.
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();
238     return;
239   }
240
241   dict->GetString(kOAuthRefreshToken, &auth_params->refresh_token);
242   dict->GetString(kOAuthClientId, &auth_params->client_id);
243   dict->GetString(kOAuthClientSecret, &auth_params->client_secret);
244 }
245
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);
254   }
255
256   // If we are restarting chrome (i.e. on crash), we need to initialize
257   // OAuth2TokenService as well.
258   InitializeTokenService();
259 }
260
261 void StartupAppLauncher::InitializeNetwork() {
262   delegate_->InitializeNetwork();
263 }
264
265 void StartupAppLauncher::InitializeTokenService() {
266   delegate_->OnInitializingTokenService();
267
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()) {
275     InitializeNetwork();
276   } else {
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.
283     //
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);
290
291     profile_token_service->UpdateCredentials(
292         "kiosk_mode@localhost",
293         auth_params_.refresh_token);
294   }
295 }
296
297 void StartupAppLauncher::OnRefreshTokenAvailable(
298     const std::string& account_id) {
299   ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
300       ->RemoveObserver(this);
301   InitializeNetwork();
302 }
303
304 void StartupAppLauncher::OnRefreshTokensLoaded() {
305   ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
306       ->RemoveObserver(this);
307   InitializeNetwork();
308 }
309
310 void StartupAppLauncher::LaunchApp() {
311   if (!ready_to_launch_) {
312     NOTREACHED();
313     LOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
314   }
315
316   const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
317       extension_service()->GetInstalledExtension(app_id_);
318   CHECK(extension);
319
320   if (!extensions::KioskModeInfo::IsKioskEnabled(extension)) {
321     OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED);
322     return;
323   }
324
325   // Always open the app in a window.
326   OpenApplication(AppLaunchParams(profile_, extension,
327                                   extensions::LAUNCH_CONTAINER_WINDOW,
328                                   NEW_WINDOW));
329   InitAppSession(profile_, app_id_);
330
331   UserManager::Get()->SessionStarted();
332
333   content::NotificationService::current()->Notify(
334       chrome::NOTIFICATION_KIOSK_APP_LAUNCHED,
335       content::NotificationService::AllSources(),
336       content::NotificationService::NoDetails());
337
338   OnLaunchSuccess();
339 }
340
341 void StartupAppLauncher::OnLaunchSuccess() {
342   delegate_->OnLaunchSucceeded();
343 }
344
345 void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) {
346   LOG(ERROR) << "App launch failed, error: " << error;
347   DCHECK_NE(KioskAppLaunchError::NONE, error);
348
349   delegate_->OnLaunchFailed(error);
350 }
351
352 void StartupAppLauncher::MaybeInstall() {
353   delegate_->OnInstallingApp();
354
355   update_checker_.reset(new AppUpdateChecker(this));
356   update_checker_->Start();
357 }
358
359 void StartupAppLauncher::OnUpdateCheckNotInstalled() {
360   BeginInstall();
361 }
362
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);
370
371   OnUpdateCheckNotInstalled();
372 }
373
374 void StartupAppLauncher::OnUpdateCheckNoUpdate() {
375   OnReadyToLaunch();
376 }
377
378 void StartupAppLauncher::BeginInstall() {
379   installer_ = new WebstoreStartupInstaller(
380       app_id_,
381       profile_,
382       false,
383       base::Bind(&StartupAppLauncher::InstallCallback, AsWeakPtr()));
384   installer_->BeginInstall();
385 }
386
387 void StartupAppLauncher::InstallCallback(bool success,
388                                          const std::string& error) {
389   installer_ = NULL;
390   if (success) {
391     // Finish initialization after the callback returns.
392     // So that the app finishes its installation.
393     BrowserThread::PostTask(
394         BrowserThread::UI,
395         FROM_HERE,
396         base::Bind(&StartupAppLauncher::OnReadyToLaunch,
397                    AsWeakPtr()));
398
399     // Schedule app data update after installation.
400     BrowserThread::PostTask(
401         BrowserThread::UI,
402         FROM_HERE,
403         base::Bind(&StartupAppLauncher::UpdateAppData,
404                    AsWeakPtr()));
405     return;
406   }
407
408   LOG(ERROR) << "App install failed: " << error;
409   OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
410 }
411
412 void StartupAppLauncher::OnReadyToLaunch() {
413   ready_to_launch_ = true;
414   delegate_->OnReadyToLaunch();
415 }
416
417 void StartupAppLauncher::UpdateAppData() {
418   KioskAppManager::Get()->ClearAppData(app_id_);
419   KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_, profile_, NULL);
420 }
421
422 }   // namespace chromeos