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/kiosk_app_launcher.h" 6 7 #include "base/chromeos/chromeos_version.h" 8 #include "base/logging.h" 9 #include "base/memory/weak_ptr.h" 10 #include "base/message_loop/message_loop.h" 11 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" 12 #include "chrome/browser/chromeos/app_mode/startup_app_launcher.h" 13 #include "chrome/browser/chromeos/login/login_display_host_impl.h" 14 #include "chrome/browser/chromeos/login/login_utils.h" 15 #include "chrome/browser/chromeos/login/user_manager.h" 16 #include "chrome/browser/chromeos/settings/cros_settings.h" 17 #include "chrome/browser/chromeos/ui/app_launch_view.h" 18 #include "chrome/browser/lifetime/application_lifetime.h" 19 #include "chromeos/cryptohome/async_method_caller.h" 20 #include "chromeos/cryptohome/cryptohome_library.h" 21 #include "chromeos/dbus/cryptohome_client.h" 22 #include "chromeos/dbus/dbus_thread_manager.h" 23 #include "content/public/browser/browser_thread.h" 24 25 using content::BrowserThread; 26 27 namespace chromeos { 28 29 namespace { 30 31 void IgnoreResult(bool mount_success, cryptohome::MountError mount_error) {} 32 33 } // namespace 34 35 // static 36 KioskAppLauncher* KioskAppLauncher::running_instance_ = NULL; 37 38 //////////////////////////////////////////////////////////////////////////////// 39 // KioskAppLauncher::CryptohomedChecker ensures cryptohome daemon is up 40 // and running by issuing an IsMounted call. If the call does not go through 41 // and chromeos::DBUS_METHOD_CALL_SUCCESS is not returned, it will retry after 42 // some time out and at the maximum five times before it gives up. Upon 43 // success, it resumes the launch by calling KioskAppLauncher::StartMount. 44 45 class KioskAppLauncher::CryptohomedChecker 46 : public base::SupportsWeakPtr<CryptohomedChecker> { 47 public: 48 explicit CryptohomedChecker(KioskAppLauncher* launcher) 49 : launcher_(launcher), 50 retry_count_(0) { 51 } 52 ~CryptohomedChecker() {} 53 54 void StartCheck() { 55 chromeos::DBusThreadManager::Get()->GetCryptohomeClient()->IsMounted( 56 base::Bind(&CryptohomedChecker::OnCryptohomeIsMounted, 57 AsWeakPtr())); 58 } 59 60 private: 61 void OnCryptohomeIsMounted(chromeos::DBusMethodCallStatus call_status, 62 bool is_mounted) { 63 if (call_status != chromeos::DBUS_METHOD_CALL_SUCCESS) { 64 const int kMaxRetryTimes = 5; 65 ++retry_count_; 66 if (retry_count_ > kMaxRetryTimes) { 67 LOG(ERROR) << "Could not talk to cryptohomed for launching kiosk app."; 68 ReportCheckResult(KioskAppLaunchError::CRYPTOHOMED_NOT_RUNNING); 69 return; 70 } 71 72 const int retry_delay_in_milliseconds = 500 * (1 << retry_count_); 73 base::MessageLoop::current()->PostDelayedTask( 74 FROM_HERE, 75 base::Bind(&CryptohomedChecker::StartCheck, AsWeakPtr()), 76 base::TimeDelta::FromMilliseconds(retry_delay_in_milliseconds)); 77 return; 78 } 79 80 if (is_mounted) 81 LOG(ERROR) << "Cryptohome is mounted before launching kiosk app."; 82 83 // Proceed only when cryptohome is not mounded or running on dev box. 84 if (!is_mounted || !base::chromeos::IsRunningOnChromeOS()) 85 ReportCheckResult(KioskAppLaunchError::NONE); 86 else 87 ReportCheckResult(KioskAppLaunchError::ALREADY_MOUNTED); 88 } 89 90 void ReportCheckResult(KioskAppLaunchError::Error error) { 91 if (error == KioskAppLaunchError::NONE) 92 launcher_->StartMount(); 93 else 94 launcher_->ReportLaunchResult(error); 95 } 96 97 KioskAppLauncher* launcher_; 98 int retry_count_; 99 100 DISALLOW_COPY_AND_ASSIGN(CryptohomedChecker); 101 }; 102 103 //////////////////////////////////////////////////////////////////////////////// 104 // KioskAppLauncher::ProfileLoader creates or loads the app profile. 105 106 class KioskAppLauncher::ProfileLoader : public LoginUtils::Delegate { 107 public: 108 ProfileLoader(KioskAppManager* kiosk_app_manager, 109 KioskAppLauncher* kiosk_app_launcher) 110 : kiosk_app_launcher_(kiosk_app_launcher), 111 user_id_(kiosk_app_launcher->user_id_) { 112 CHECK(!user_id_.empty()); 113 } 114 115 virtual ~ProfileLoader() { 116 LoginUtils::Get()->DelegateDeleted(this); 117 } 118 119 void Start() { 120 cryptohome::AsyncMethodCaller::GetInstance()->AsyncGetSanitizedUsername( 121 user_id_, 122 base::Bind(&ProfileLoader::OnUsernameHashRetrieved, 123 base::Unretained(this))); 124 } 125 126 private: 127 void OnUsernameHashRetrieved(bool success, 128 const std::string& username_hash) { 129 if (!success) { 130 LOG(ERROR) << "Unable to retrieve username hash for user '" << user_id_ 131 << "'."; 132 kiosk_app_launcher_->ReportLaunchResult( 133 KioskAppLaunchError::UNABLE_TO_RETRIEVE_HASH); 134 return; 135 } 136 LoginUtils::Get()->PrepareProfile( 137 UserContext(user_id_, 138 std::string(), // password 139 std::string(), // auth_code 140 username_hash), 141 std::string(), // display email 142 false, // using_oauth 143 false, // has_cookies 144 false, // has_active_session 145 this); 146 } 147 148 // LoginUtils::Delegate overrides: 149 virtual void OnProfilePrepared(Profile* profile) OVERRIDE { 150 kiosk_app_launcher_->OnProfilePrepared(profile); 151 } 152 153 KioskAppLauncher* kiosk_app_launcher_; 154 std::string user_id_; 155 156 DISALLOW_COPY_AND_ASSIGN(ProfileLoader); 157 }; 158 159 //////////////////////////////////////////////////////////////////////////////// 160 // KioskAppLauncher 161 162 KioskAppLauncher::KioskAppLauncher(KioskAppManager* kiosk_app_manager, 163 const std::string& app_id) 164 : kiosk_app_manager_(kiosk_app_manager), 165 app_id_(app_id), 166 remove_attempted_(false) { 167 KioskAppManager::App app; 168 CHECK(kiosk_app_manager_->GetApp(app_id_, &app)); 169 user_id_ = app.user_id; 170 } 171 172 KioskAppLauncher::~KioskAppLauncher() {} 173 174 void KioskAppLauncher::Start() { 175 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 176 177 if (running_instance_) { 178 LOG(WARNING) << "Unable to launch " << app_id_ << "with a pending launch."; 179 ReportLaunchResult(KioskAppLaunchError::HAS_PENDING_LAUNCH); 180 return; 181 } 182 183 running_instance_ = this; // Reset in ReportLaunchResult. 184 185 // Show app launch splash. The spash is removed either after a successful 186 // launch or chrome exit because of launch failure. 187 chromeos::ShowAppLaunchSplashScreen(app_id_); 188 189 // Check cryptohomed. If all goes good, flow goes to StartMount. Otherwise 190 // it goes to ReportLaunchResult with failure. 191 crytohomed_checker.reset(new CryptohomedChecker(this)); 192 crytohomed_checker->StartCheck(); 193 } 194 195 void KioskAppLauncher::ReportLaunchResult(KioskAppLaunchError::Error error) { 196 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 197 198 running_instance_ = NULL; 199 200 if (error != KioskAppLaunchError::NONE) { 201 // Saves the error and ends the session to go back to login screen. 202 KioskAppLaunchError::Save(error); 203 chrome::AttemptUserExit(); 204 } 205 206 delete this; 207 } 208 209 void KioskAppLauncher::StartMount() { 210 // Nuke old home that uses |app_id_| as cryptohome user id. 211 // TODO(xiyuan): Remove this after all clients migrated to new home. 212 cryptohome::AsyncMethodCaller::GetInstance()->AsyncRemove( 213 app_id_, 214 base::Bind(&IgnoreResult)); 215 216 cryptohome::AsyncMethodCaller::GetInstance()->AsyncMountPublic( 217 user_id_, 218 cryptohome::CREATE_IF_MISSING, 219 base::Bind(&KioskAppLauncher::MountCallback, 220 base::Unretained(this))); 221 } 222 223 void KioskAppLauncher::MountCallback(bool mount_success, 224 cryptohome::MountError mount_error) { 225 if (mount_success) { 226 profile_loader_.reset(new ProfileLoader(kiosk_app_manager_, this)); 227 profile_loader_->Start(); 228 return; 229 } 230 231 if (!remove_attempted_) { 232 LOG(ERROR) << "Attempt to remove app cryptohome because of mount failure" 233 << ", mount error=" << mount_error; 234 235 remove_attempted_ = true; 236 AttemptRemove(); 237 return; 238 } 239 240 LOG(ERROR) << "Failed to mount app cryptohome, error=" << mount_error; 241 ReportLaunchResult(KioskAppLaunchError::UNABLE_TO_MOUNT); 242 } 243 244 void KioskAppLauncher::AttemptRemove() { 245 cryptohome::AsyncMethodCaller::GetInstance()->AsyncRemove( 246 user_id_, 247 base::Bind(&KioskAppLauncher::RemoveCallback, 248 base::Unretained(this))); 249 } 250 251 void KioskAppLauncher::RemoveCallback(bool success, 252 cryptohome::MountError return_code) { 253 if (success) { 254 StartMount(); 255 return; 256 } 257 258 LOG(ERROR) << "Failed to remove app cryptohome, erro=" << return_code; 259 ReportLaunchResult(KioskAppLaunchError::UNABLE_TO_REMOVE); 260 } 261 262 void KioskAppLauncher::OnProfilePrepared(Profile* profile) { 263 // StartupAppLauncher deletes itself when done. 264 (new chromeos::StartupAppLauncher(profile, app_id_))->Start(); 265 266 if (LoginDisplayHostImpl::default_host()) 267 LoginDisplayHostImpl::default_host()->Finalize(); 268 UserManager::Get()->SessionStarted(); 269 270 ReportLaunchResult(KioskAppLaunchError::NONE); 271 } 272 273 } // namespace chromeos 274