1// Copyright (c) 2012 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/login/screen_locker.h"
6
7#include <string>
8#include <vector>
9
10#include "ash/ash_switches.h"
11#include "ash/desktop_background/desktop_background_controller.h"
12#include "ash/shell.h"
13#include "ash/wm/lock_state_controller.h"
14#include "base/bind.h"
15#include "base/command_line.h"
16#include "base/lazy_instance.h"
17#include "base/memory/weak_ptr.h"
18#include "base/message_loop/message_loop.h"
19#include "base/metrics/histogram.h"
20#include "base/strings/string_util.h"
21#include "base/timer/timer.h"
22#include "chrome/browser/chrome_notification_types.h"
23#include "chrome/browser/chromeos/login/authenticator.h"
24#include "chrome/browser/chromeos/login/login_performer.h"
25#include "chrome/browser/chromeos/login/login_utils.h"
26#include "chrome/browser/chromeos/login/user_adding_screen.h"
27#include "chrome/browser/chromeos/login/user_manager.h"
28#include "chrome/browser/chromeos/login/webui_screen_locker.h"
29#include "chrome/browser/lifetime/application_lifetime.h"
30#include "chrome/browser/profiles/profile.h"
31#include "chrome/browser/profiles/profile_manager.h"
32#include "chrome/browser/signin/signin_manager.h"
33#include "chrome/browser/signin/signin_manager_factory.h"
34#include "chrome/browser/sync/profile_sync_service.h"
35#include "chrome/browser/sync/profile_sync_service_factory.h"
36#include "chrome/browser/ui/browser.h"
37#include "chrome/browser/ui/browser_commands.h"
38#include "chrome/browser/ui/browser_finder.h"
39#include "chrome/browser/ui/browser_window.h"
40#include "chrome/common/chrome_switches.h"
41#include "chromeos/dbus/dbus_thread_manager.h"
42#include "chromeos/dbus/session_manager_client.h"
43#include "content/public/browser/browser_thread.h"
44#include "content/public/browser/notification_service.h"
45#include "content/public/browser/user_metrics.h"
46#include "grit/generated_resources.h"
47#include "ui/base/l10n/l10n_util.h"
48#include "url/gurl.h"
49
50using content::BrowserThread;
51using content::UserMetricsAction;
52
53namespace {
54
55// Timeout for unlock animation guard - some animations may be required to run
56// on successful authentication before unlocking, but we want to be sure that
57// unlock happens even if animations are broken.
58const int kUnlockGuardTimeoutMs = 400;
59
60// Observer to start ScreenLocker when the screen lock
61class ScreenLockObserver : public chromeos::SessionManagerClient::Observer,
62                           public content::NotificationObserver,
63                           public chromeos::UserAddingScreen::Observer {
64 public:
65  ScreenLockObserver() : session_started_(false) {
66    registrar_.Add(this,
67                   chrome::NOTIFICATION_LOGIN_USER_CHANGED,
68                   content::NotificationService::AllSources());
69    registrar_.Add(this,
70                   chrome::NOTIFICATION_SESSION_STARTED,
71                   content::NotificationService::AllSources());
72  }
73
74  // NotificationObserver overrides:
75  virtual void Observe(int type,
76                       const content::NotificationSource& source,
77                       const content::NotificationDetails& details) OVERRIDE {
78    switch (type) {
79      case chrome::NOTIFICATION_LOGIN_USER_CHANGED: {
80        // Register Screen Lock only after a user has logged in.
81        chromeos::SessionManagerClient* session_manager =
82            chromeos::DBusThreadManager::Get()->GetSessionManagerClient();
83        if (!session_manager->HasObserver(this))
84          session_manager->AddObserver(this);
85        break;
86      }
87
88      case chrome::NOTIFICATION_SESSION_STARTED: {
89        session_started_ = true;
90        break;
91      }
92
93      default:
94        NOTREACHED();
95    }
96  }
97
98  virtual void LockScreen() OVERRIDE {
99    VLOG(1) << "Received LockScreen D-Bus signal from session manager";
100    if (chromeos::UserAddingScreen::Get()->IsRunning()) {
101      VLOG(1) << "Waiting for user adding screen to stop";
102      chromeos::UserAddingScreen::Get()->AddObserver(this);
103      chromeos::UserAddingScreen::Get()->Cancel();
104      return;
105    }
106    if (session_started_ &&
107        chromeos::UserManager::Get()->CanCurrentUserLock()) {
108      chromeos::ScreenLocker::Show();
109    } else {
110      // If the current user's session cannot be locked or the user has not
111      // completed all sign-in steps yet, log out instead. The latter is done to
112      // avoid complications with displaying the lock screen over the login
113      // screen while remaining secure in the case the user walks away during
114      // the sign-in steps. See crbug.com/112225 and crbug.com/110933.
115      VLOG(1) << "Calling session manager's StopSession D-Bus method";
116      chromeos::DBusThreadManager::Get()->
117          GetSessionManagerClient()->StopSession();
118    }
119  }
120
121  virtual void UnlockScreen() OVERRIDE {
122    VLOG(1) << "Received UnlockScreen D-Bus signal from session manager";
123    chromeos::ScreenLocker::Hide();
124  }
125
126  virtual void OnUserAddingFinished() OVERRIDE {
127    chromeos::UserAddingScreen::Get()->RemoveObserver(this);
128    LockScreen();
129  }
130
131 private:
132  bool session_started_;
133  content::NotificationRegistrar registrar_;
134  std::string saved_previous_input_method_id_;
135  std::string saved_current_input_method_id_;
136  std::vector<std::string> saved_active_input_method_list_;
137
138  DISALLOW_COPY_AND_ASSIGN(ScreenLockObserver);
139};
140
141static base::LazyInstance<ScreenLockObserver> g_screen_lock_observer =
142    LAZY_INSTANCE_INITIALIZER;
143
144}  // namespace
145
146namespace chromeos {
147
148// static
149ScreenLocker* ScreenLocker::screen_locker_ = NULL;
150
151//////////////////////////////////////////////////////////////////////////////
152// ScreenLocker, public:
153
154ScreenLocker::ScreenLocker(const UserList& users)
155    : users_(users),
156      locked_(false),
157      start_time_(base::Time::Now()),
158      login_status_consumer_(NULL),
159      incorrect_passwords_count_(0),
160      weak_factory_(this) {
161  DCHECK(!screen_locker_);
162  screen_locker_ = this;
163}
164
165void ScreenLocker::Init() {
166  authenticator_ = LoginUtils::Get()->CreateAuthenticator(this);
167  delegate_.reset(new WebUIScreenLocker(this));
168  delegate_->LockScreen();
169}
170
171void ScreenLocker::OnLoginFailure(const LoginFailure& error) {
172  content::RecordAction(UserMetricsAction("ScreenLocker_OnLoginFailure"));
173  if (authentication_start_time_.is_null()) {
174    LOG(ERROR) << "Start time is not set at authentication failure";
175  } else {
176    base::TimeDelta delta = base::Time::Now() - authentication_start_time_;
177    VLOG(1) << "Authentication failure: " << delta.InSecondsF() << " second(s)";
178    UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationFailureTime", delta);
179  }
180
181  EnableInput();
182  // Don't enable signout button here as we're showing
183  // MessageBubble.
184
185  delegate_->ShowErrorMessage(incorrect_passwords_count_++ ?
186                                  IDS_LOGIN_ERROR_AUTHENTICATING_2ND_TIME :
187                                  IDS_LOGIN_ERROR_AUTHENTICATING,
188                              HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT);
189
190  if (login_status_consumer_)
191    login_status_consumer_->OnLoginFailure(error);
192}
193
194void ScreenLocker::OnLoginSuccess(
195    const UserContext& user_context,
196    bool pending_requests,
197    bool using_oauth) {
198  incorrect_passwords_count_ = 0;
199  if (authentication_start_time_.is_null()) {
200    if (!user_context.username.empty())
201      LOG(ERROR) << "Start time is not set at authentication success";
202  } else {
203    base::TimeDelta delta = base::Time::Now() - authentication_start_time_;
204    VLOG(1) << "Authentication success: " << delta.InSecondsF() << " second(s)";
205    UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationSuccessTime", delta);
206  }
207
208  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kMultiProfiles)) {
209    // TODO(dzhioev): It seems like this branch never executed and should be
210    // removed before multi-profile enabling.
211    Profile* profile = ProfileManager::GetDefaultProfile();
212    if (profile && !user_context.password.empty()) {
213      // We have a non-empty password, so notify listeners (such as the sync
214      // engine).
215      SigninManagerBase* signin = SigninManagerFactory::GetForProfile(profile);
216      DCHECK(signin);
217      GoogleServiceSigninSuccessDetails details(
218          signin->GetAuthenticatedUsername(),
219          user_context.password);
220      content::NotificationService::current()->Notify(
221          chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL,
222          content::Source<Profile>(profile),
223          content::Details<const GoogleServiceSigninSuccessDetails>(&details));
224    }
225  }
226
227  if (const User* user = UserManager::Get()->FindUser(user_context.username)) {
228    if (!user->is_active())
229      UserManager::Get()->SwitchActiveUser(user_context.username);
230  } else {
231    NOTREACHED() << "Logged in user not found.";
232  }
233
234  authentication_capture_.reset(new AuthenticationParametersCapture());
235  authentication_capture_->username = user_context.username;
236  authentication_capture_->pending_requests = pending_requests;
237  authentication_capture_->using_oauth = using_oauth;
238
239  CommandLine* command_line = CommandLine::ForCurrentProcess();
240  if (command_line->HasSwitch(ash::switches::kAshDisableNewLockAnimations)) {
241    UnlockOnLoginSuccess();
242  } else {
243    // Add guard for case when something get broken in call chain to unlock
244    // for sure.
245    base::MessageLoop::current()->PostDelayedTask(
246        FROM_HERE,
247        base::Bind(&ScreenLocker::UnlockOnLoginSuccess,
248            weak_factory_.GetWeakPtr()),
249        base::TimeDelta::FromMilliseconds(kUnlockGuardTimeoutMs));
250    delegate_->AnimateAuthenticationSuccess();
251  }
252}
253
254void ScreenLocker::UnlockOnLoginSuccess() {
255  DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI);
256  if (!authentication_capture_.get()) {
257    LOG(WARNING) << "Call to UnlockOnLoginSuccess without previous " <<
258      "authentication success.";
259    return;
260  }
261
262  VLOG(1) << "Calling session manager's UnlockScreen D-Bus method";
263  DBusThreadManager::Get()->GetSessionManagerClient()->RequestUnlockScreen();
264
265  if (login_status_consumer_) {
266    login_status_consumer_->OnLoginSuccess(
267        UserContext(authentication_capture_->username,
268                    std::string(),   // password
269                    std::string()),  // auth_code
270        authentication_capture_->pending_requests,
271        authentication_capture_->using_oauth);
272  }
273  authentication_capture_.reset();
274  weak_factory_.InvalidateWeakPtrs();
275}
276
277void ScreenLocker::Authenticate(const UserContext& user_context) {
278  LOG_ASSERT(IsUserLoggedIn(user_context.username))
279      << "Invalid user trying to unlock.";
280  authentication_start_time_ = base::Time::Now();
281  delegate_->SetInputEnabled(false);
282  delegate_->OnAuthenticate();
283
284  BrowserThread::PostTask(
285      BrowserThread::UI, FROM_HERE,
286      base::Bind(&Authenticator::AuthenticateToUnlock,
287                 authenticator_.get(),
288                 user_context));
289}
290
291void ScreenLocker::AuthenticateByPassword(const std::string& password) {
292  LOG_ASSERT(users_.size() == 1);
293  Authenticate(UserContext(users_[0]->email(), password, ""));
294}
295
296void ScreenLocker::ClearErrors() {
297  delegate_->ClearErrors();
298}
299
300void ScreenLocker::EnableInput() {
301  delegate_->SetInputEnabled(true);
302}
303
304void ScreenLocker::Signout() {
305  delegate_->ClearErrors();
306  content::RecordAction(UserMetricsAction("ScreenLocker_Signout"));
307  // We expect that this call will not wait for any user input.
308  // If it changes at some point, we will need to force exit.
309  chrome::AttemptUserExit();
310
311  // Don't hide yet the locker because the chrome screen may become visible
312  // briefly.
313}
314
315void ScreenLocker::ShowErrorMessage(int error_msg_id,
316                                    HelpAppLauncher::HelpTopic help_topic_id,
317                                    bool sign_out_only) {
318  delegate_->SetInputEnabled(!sign_out_only);
319  delegate_->ShowErrorMessage(error_msg_id, help_topic_id);
320}
321
322void ScreenLocker::SetLoginStatusConsumer(
323    chromeos::LoginStatusConsumer* consumer) {
324  login_status_consumer_ = consumer;
325}
326
327// static
328void ScreenLocker::Show() {
329  content::RecordAction(UserMetricsAction("ScreenLocker_Show"));
330  DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI);
331
332  // Check whether the currently logged in user is a guest account and if so,
333  // refuse to lock the screen (crosbug.com/23764).
334  // For a demo user, we should never show the lock screen (crosbug.com/27647).
335  if (UserManager::Get()->IsLoggedInAsGuest() ||
336      UserManager::Get()->IsLoggedInAsDemoUser()) {
337    VLOG(1) << "Refusing to lock screen for guest/demo account";
338    return;
339  }
340
341  // Exit fullscreen.
342  Browser* browser = chrome::FindLastActiveWithHostDesktopType(
343      chrome::HOST_DESKTOP_TYPE_ASH);
344  // browser can be NULL if we receive a lock request before the first browser
345  // window is shown.
346  if (browser && browser->window()->IsFullscreen()) {
347    chrome::ToggleFullscreenMode(browser);
348  }
349
350  if (!screen_locker_) {
351    ScreenLocker* locker =
352        new ScreenLocker(UserManager::Get()->GetLRULoggedInUsers());
353    VLOG(1) << "Created ScreenLocker " << locker;
354    locker->Init();
355  } else {
356    VLOG(1) << "ScreenLocker " << screen_locker_ << " already exists; "
357            << " calling session manager's HandleLockScreenShown D-Bus method";
358    DBusThreadManager::Get()->GetSessionManagerClient()->
359        NotifyLockScreenShown();
360  }
361}
362
363// static
364void ScreenLocker::Hide() {
365  DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI);
366  // For a guest/demo user, screen_locker_ would have never been initialized.
367  if (UserManager::Get()->IsLoggedInAsGuest() ||
368      UserManager::Get()->IsLoggedInAsDemoUser()) {
369    VLOG(1) << "Refusing to hide lock screen for guest/demo account";
370    return;
371  }
372
373  DCHECK(screen_locker_);
374  base::Callback<void(void)> callback =
375      base::Bind(&ScreenLocker::ScheduleDeletion);
376  ash::Shell::GetInstance()->lock_state_controller()->
377    OnLockScreenHide(callback);
378}
379
380void ScreenLocker::ScheduleDeletion() {
381  // Avoid possible multiple calls.
382  if (screen_locker_ == NULL)
383    return;
384  VLOG(1) << "Posting task to delete ScreenLocker " << screen_locker_;
385  ScreenLocker* screen_locker = screen_locker_;
386  screen_locker_ = NULL;
387  base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE, screen_locker);
388}
389
390// static
391void ScreenLocker::InitClass() {
392  g_screen_lock_observer.Get();
393}
394
395////////////////////////////////////////////////////////////////////////////////
396// ScreenLocker, private:
397
398ScreenLocker::~ScreenLocker() {
399  VLOG(1) << "Destroying ScreenLocker " << this;
400  DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI);
401
402  if (authenticator_.get())
403    authenticator_->SetConsumer(NULL);
404  ClearErrors();
405
406  VLOG(1) << "Moving desktop background to unlocked container";
407  ash::Shell::GetInstance()->
408      desktop_background_controller()->MoveDesktopToUnlockedContainer();
409
410  screen_locker_ = NULL;
411  bool state = false;
412  VLOG(1) << "Emitting SCREEN_LOCK_STATE_CHANGED with state=" << state;
413  content::NotificationService::current()->Notify(
414      chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
415      content::Source<ScreenLocker>(this),
416      content::Details<bool>(&state));
417  VLOG(1) << "Calling session manager's HandleLockScreenDismissed D-Bus method";
418  DBusThreadManager::Get()->GetSessionManagerClient()->
419      NotifyLockScreenDismissed();
420}
421
422void ScreenLocker::SetAuthenticator(Authenticator* authenticator) {
423  authenticator_ = authenticator;
424}
425
426void ScreenLocker::ScreenLockReady() {
427  locked_ = true;
428  base::TimeDelta delta = base::Time::Now() - start_time_;
429  VLOG(1) << "ScreenLocker " << this << " is ready after "
430          << delta.InSecondsF() << " second(s)";
431  UMA_HISTOGRAM_TIMES("ScreenLocker.ScreenLockTime", delta);
432
433  VLOG(1) << "Moving desktop background to locked container";
434  ash::Shell::GetInstance()->
435      desktop_background_controller()->MoveDesktopToLockedContainer();
436
437  bool state = true;
438  VLOG(1) << "Emitting SCREEN_LOCK_STATE_CHANGED with state=" << state;
439  content::NotificationService::current()->Notify(
440      chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
441      content::Source<ScreenLocker>(this),
442      content::Details<bool>(&state));
443  VLOG(1) << "Calling session manager's HandleLockScreenShown D-Bus method";
444  DBusThreadManager::Get()->GetSessionManagerClient()->NotifyLockScreenShown();
445}
446
447content::WebUI* ScreenLocker::GetAssociatedWebUI() {
448  return delegate_->GetAssociatedWebUI();
449}
450
451bool ScreenLocker::IsUserLoggedIn(const std::string& username) {
452  for (UserList::const_iterator it = users_.begin(); it != users_.end(); ++it) {
453    if ((*it)->email() == username)
454      return true;
455  }
456  return false;
457}
458
459}  // namespace chromeos
460
461