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_profile_loader.h"
6
7#include "base/logging.h"
8#include "base/memory/weak_ptr.h"
9#include "base/message_loop/message_loop.h"
10#include "base/strings/string_util.h"
11#include "base/sys_info.h"
12#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
13#include "chrome/browser/chromeos/login/demo_mode/demo_app_launcher.h"
14#include "chrome/browser/chromeos/login/login_utils.h"
15#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
16#include "chrome/browser/chromeos/settings/cros_settings.h"
17#include "chrome/browser/lifetime/application_lifetime.h"
18#include "chromeos/cryptohome/async_method_caller.h"
19#include "chromeos/dbus/cryptohome_client.h"
20#include "chromeos/dbus/dbus_thread_manager.h"
21#include "chromeos/login/auth/auth_status_consumer.h"
22#include "chromeos/login/auth/user_context.h"
23#include "chromeos/login/user_names.h"
24#include "content/public/browser/browser_thread.h"
25#include "google_apis/gaia/gaia_auth_util.h"
26
27using content::BrowserThread;
28
29namespace chromeos {
30
31namespace {
32
33KioskAppLaunchError::Error LoginFailureToKioskAppLaunchError(
34    const AuthFailure& error) {
35  switch (error.reason()) {
36    case AuthFailure::COULD_NOT_MOUNT_TMPFS:
37    case AuthFailure::COULD_NOT_MOUNT_CRYPTOHOME:
38      return KioskAppLaunchError::UNABLE_TO_MOUNT;
39    case AuthFailure::DATA_REMOVAL_FAILED:
40      return KioskAppLaunchError::UNABLE_TO_REMOVE;
41    case AuthFailure::USERNAME_HASH_FAILED:
42      return KioskAppLaunchError::UNABLE_TO_RETRIEVE_HASH;
43    default:
44      NOTREACHED();
45      return KioskAppLaunchError::UNABLE_TO_MOUNT;
46  }
47}
48
49}  // namespace
50
51////////////////////////////////////////////////////////////////////////////////
52// KioskProfileLoader::CryptohomedChecker ensures cryptohome daemon is up
53// and running by issuing an IsMounted call. If the call does not go through
54// and chromeos::DBUS_METHOD_CALL_SUCCESS is not returned, it will retry after
55// some time out and at the maximum five times before it gives up. Upon
56// success, it resumes the launch by logging in as a kiosk mode account.
57
58class KioskProfileLoader::CryptohomedChecker
59    : public base::SupportsWeakPtr<CryptohomedChecker> {
60 public:
61  explicit CryptohomedChecker(KioskProfileLoader* loader)
62      : loader_(loader),
63        retry_count_(0) {
64  }
65  ~CryptohomedChecker() {}
66
67  void StartCheck() {
68    chromeos::DBusThreadManager::Get()->GetCryptohomeClient()->IsMounted(
69        base::Bind(&CryptohomedChecker::OnCryptohomeIsMounted,
70                   AsWeakPtr()));
71  }
72
73 private:
74  void OnCryptohomeIsMounted(chromeos::DBusMethodCallStatus call_status,
75                             bool is_mounted) {
76    if (call_status != chromeos::DBUS_METHOD_CALL_SUCCESS) {
77      const int kMaxRetryTimes = 5;
78      ++retry_count_;
79      if (retry_count_ > kMaxRetryTimes) {
80        LOG(ERROR) << "Could not talk to cryptohomed for launching kiosk app.";
81        ReportCheckResult(KioskAppLaunchError::CRYPTOHOMED_NOT_RUNNING);
82        return;
83      }
84
85      const int retry_delay_in_milliseconds = 500 * (1 << retry_count_);
86      base::MessageLoop::current()->PostDelayedTask(
87          FROM_HERE,
88          base::Bind(&CryptohomedChecker::StartCheck, AsWeakPtr()),
89          base::TimeDelta::FromMilliseconds(retry_delay_in_milliseconds));
90      return;
91    }
92
93    if (is_mounted)
94      LOG(ERROR) << "Cryptohome is mounted before launching kiosk app.";
95
96    // Proceed only when cryptohome is not mounded or running on dev box.
97    if (!is_mounted || !base::SysInfo::IsRunningOnChromeOS())
98      ReportCheckResult(KioskAppLaunchError::NONE);
99    else
100      ReportCheckResult(KioskAppLaunchError::ALREADY_MOUNTED);
101  }
102
103  void ReportCheckResult(KioskAppLaunchError::Error error) {
104    if (error == KioskAppLaunchError::NONE)
105      loader_->LoginAsKioskAccount();
106    else
107      loader_->ReportLaunchResult(error);
108  }
109
110  KioskProfileLoader* loader_;
111  int retry_count_;
112
113  DISALLOW_COPY_AND_ASSIGN(CryptohomedChecker);
114};
115
116
117////////////////////////////////////////////////////////////////////////////////
118// KioskProfileLoader
119
120KioskProfileLoader::KioskProfileLoader(const std::string& app_user_id,
121                                       bool use_guest_mount,
122                                       Delegate* delegate)
123    : user_id_(app_user_id),
124      use_guest_mount_(use_guest_mount),
125      delegate_(delegate) {}
126
127KioskProfileLoader::~KioskProfileLoader() {}
128
129void KioskProfileLoader::Start() {
130  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
131  login_performer_.reset();
132  cryptohomed_checker_.reset(new CryptohomedChecker(this));
133  cryptohomed_checker_->StartCheck();
134}
135
136void KioskProfileLoader::LoginAsKioskAccount() {
137  login_performer_.reset(new LoginPerformer(this));
138  login_performer_->LoginAsKioskAccount(user_id_, use_guest_mount_);
139}
140
141void KioskProfileLoader::ReportLaunchResult(KioskAppLaunchError::Error error) {
142  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
143
144  if (error != KioskAppLaunchError::NONE) {
145    delegate_->OnProfileLoadFailed(error);
146  }
147}
148
149void KioskProfileLoader::OnAuthSuccess(const UserContext& user_context) {
150  // LoginPerformer will delete itself.
151  login_performer_->set_delegate(NULL);
152  ignore_result(login_performer_.release());
153
154  // If we are launching a demo session, we need to start MountGuest with the
155  // guest username; this is because there are several places in the cros code
156  // which rely on the username sent to cryptohome to be $guest. Back in Chrome
157  // we switch this back to the demo user name to correctly identify this
158  // user as a demo user.
159  UserContext context = user_context;
160  if (context.GetUserID() == chromeos::login::kGuestUserName)
161    context.SetUserID(DemoAppLauncher::kDemoUserName);
162  LoginUtils::Get()->PrepareProfile(context,
163                                    false,  // has_auth_cookies
164                                    false,  // has_active_session
165                                    this);
166}
167
168void KioskProfileLoader::OnAuthFailure(const AuthFailure& error) {
169  ReportLaunchResult(LoginFailureToKioskAppLaunchError(error));
170}
171
172void KioskProfileLoader::WhiteListCheckFailed(const std::string& email) {
173  NOTREACHED();
174}
175
176void KioskProfileLoader::PolicyLoadFailed() {
177  ReportLaunchResult(KioskAppLaunchError::POLICY_LOAD_FAILED);
178}
179
180void KioskProfileLoader::OnOnlineChecked(
181    const std::string& email, bool success) {
182  NOTREACHED();
183}
184
185void KioskProfileLoader::OnProfilePrepared(Profile* profile) {
186  // This object could be deleted any time after successfully reporting
187  // a profile load, so invalidate the LoginUtils delegate now.
188  LoginUtils::Get()->DelegateDeleted(this);
189
190  delegate_->OnProfileLoaded(profile);
191  ReportLaunchResult(KioskAppLaunchError::NONE);
192}
193
194}  // namespace chromeos
195