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