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