Home | History | Annotate | Download | only in app_mode
      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