| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/login/screen_locker.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "ash/ash_switches.h" |
| #include "ash/desktop_background/desktop_background_controller.h" |
| #include "ash/shell.h" |
| #include "ash/wm/lock_state_controller.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/histogram.h" |
| #include "base/strings/string_util.h" |
| #include "base/timer/timer.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/chromeos/accessibility/accessibility_manager.h" |
| #include "chrome/browser/chromeos/login/authenticator.h" |
| #include "chrome/browser/chromeos/login/login_performer.h" |
| #include "chrome/browser/chromeos/login/login_utils.h" |
| #include "chrome/browser/chromeos/login/user_adding_screen.h" |
| #include "chrome/browser/chromeos/login/user_manager.h" |
| #include "chrome/browser/chromeos/login/webui_screen_locker.h" |
| #include "chrome/browser/lifetime/application_lifetime.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/signin/signin_manager.h" |
| #include "chrome/browser/signin/signin_manager_factory.h" |
| #include "chrome/browser/sync/profile_sync_service.h" |
| #include "chrome/browser/sync/profile_sync_service_factory.h" |
| #include "chrome/browser/ui/webui/chromeos/login/screenlock_icon_provider.h" |
| #include "chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chromeos/audio/chromeos_sounds.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/session_manager_client.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/url_data_source.h" |
| #include "content/public/browser/user_metrics.h" |
| #include "grit/browser_resources.h" |
| #include "grit/generated_resources.h" |
| #include "media/audio/sounds/sounds_manager.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/image/image.h" |
| #include "url/gurl.h" |
| |
| using content::BrowserThread; |
| using content::UserMetricsAction; |
| |
| namespace { |
| |
| // Timeout for unlock animation guard - some animations may be required to run |
| // on successful authentication before unlocking, but we want to be sure that |
| // unlock happens even if animations are broken. |
| const int kUnlockGuardTimeoutMs = 400; |
| |
| // Observer to start ScreenLocker when the screen lock |
| class ScreenLockObserver : public chromeos::SessionManagerClient::Observer, |
| public content::NotificationObserver, |
| public chromeos::UserAddingScreen::Observer { |
| public: |
| ScreenLockObserver() : session_started_(false) { |
| registrar_.Add(this, |
| chrome::NOTIFICATION_LOGIN_USER_CHANGED, |
| content::NotificationService::AllSources()); |
| registrar_.Add(this, |
| chrome::NOTIFICATION_SESSION_STARTED, |
| content::NotificationService::AllSources()); |
| } |
| |
| // NotificationObserver overrides: |
| virtual void Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) OVERRIDE { |
| switch (type) { |
| case chrome::NOTIFICATION_LOGIN_USER_CHANGED: { |
| // Register Screen Lock only after a user has logged in. |
| chromeos::SessionManagerClient* session_manager = |
| chromeos::DBusThreadManager::Get()->GetSessionManagerClient(); |
| if (!session_manager->HasObserver(this)) |
| session_manager->AddObserver(this); |
| break; |
| } |
| |
| case chrome::NOTIFICATION_SESSION_STARTED: { |
| session_started_ = true; |
| break; |
| } |
| |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| virtual void LockScreen() OVERRIDE { |
| VLOG(1) << "Received LockScreen D-Bus signal from session manager"; |
| if (chromeos::UserAddingScreen::Get()->IsRunning()) { |
| VLOG(1) << "Waiting for user adding screen to stop"; |
| chromeos::UserAddingScreen::Get()->AddObserver(this); |
| chromeos::UserAddingScreen::Get()->Cancel(); |
| return; |
| } |
| if (session_started_ && |
| chromeos::UserManager::Get()->CanCurrentUserLock()) { |
| chromeos::ScreenLocker::Show(); |
| } else { |
| // If the current user's session cannot be locked or the user has not |
| // completed all sign-in steps yet, log out instead. The latter is done to |
| // avoid complications with displaying the lock screen over the login |
| // screen while remaining secure in the case the user walks away during |
| // the sign-in steps. See crbug.com/112225 and crbug.com/110933. |
| VLOG(1) << "Calling session manager's StopSession D-Bus method"; |
| chromeos::DBusThreadManager::Get()-> |
| GetSessionManagerClient()->StopSession(); |
| } |
| } |
| |
| virtual void OnUserAddingFinished() OVERRIDE { |
| chromeos::UserAddingScreen::Get()->RemoveObserver(this); |
| LockScreen(); |
| } |
| |
| private: |
| bool session_started_; |
| content::NotificationRegistrar registrar_; |
| std::string saved_previous_input_method_id_; |
| std::string saved_current_input_method_id_; |
| std::vector<std::string> saved_active_input_method_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScreenLockObserver); |
| }; |
| |
| void PlaySound(int sound_key) { |
| if (chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) |
| media::SoundsManager::Get()->Play(sound_key); |
| } |
| |
| static base::LazyInstance<ScreenLockObserver> g_screen_lock_observer = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| } // namespace |
| |
| namespace chromeos { |
| |
| // static |
| ScreenLocker* ScreenLocker::screen_locker_ = NULL; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // ScreenLocker, public: |
| |
| ScreenLocker::ScreenLocker(const UserList& users) |
| : users_(users), |
| locked_(false), |
| start_time_(base::Time::Now()), |
| login_status_consumer_(NULL), |
| incorrect_passwords_count_(0), |
| weak_factory_(this) { |
| DCHECK(!screen_locker_); |
| screen_locker_ = this; |
| |
| ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| media::SoundsManager* manager = media::SoundsManager::Get(); |
| manager->Initialize(SOUND_LOCK, |
| bundle.GetRawDataResource(IDR_SOUND_LOCK_WAV)); |
| manager->Initialize(SOUND_UNLOCK, |
| bundle.GetRawDataResource(IDR_SOUND_UNLOCK_WAV)); |
| |
| ash::Shell::GetInstance()-> |
| lock_state_controller()-> |
| SetLockScreenDisplayedCallback( |
| base::Bind(&PlaySound, static_cast<int>(chromeos::SOUND_LOCK))); |
| } |
| |
| void ScreenLocker::Init() { |
| authenticator_ = LoginUtils::Get()->CreateAuthenticator(this); |
| delegate_.reset(new WebUIScreenLocker(this)); |
| delegate_->LockScreen(); |
| |
| // Ownership of |icon_image_source| is passed. |
| screenlock_icon_provider_.reset(new ScreenlockIconProvider); |
| ScreenlockIconSource* screenlock_icon_source = |
| new ScreenlockIconSource(screenlock_icon_provider_->AsWeakPtr()); |
| content::URLDataSource::Add( |
| Profile::FromWebUI(GetAssociatedWebUI()), |
| screenlock_icon_source); |
| } |
| |
| void ScreenLocker::OnLoginFailure(const LoginFailure& error) { |
| content::RecordAction(UserMetricsAction("ScreenLocker_OnLoginFailure")); |
| if (authentication_start_time_.is_null()) { |
| LOG(ERROR) << "Start time is not set at authentication failure"; |
| } else { |
| base::TimeDelta delta = base::Time::Now() - authentication_start_time_; |
| VLOG(1) << "Authentication failure: " << delta.InSecondsF() << " second(s)"; |
| UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationFailureTime", delta); |
| } |
| |
| EnableInput(); |
| // Don't enable signout button here as we're showing |
| // MessageBubble. |
| |
| delegate_->ShowErrorMessage(incorrect_passwords_count_++ ? |
| IDS_LOGIN_ERROR_AUTHENTICATING_2ND_TIME : |
| IDS_LOGIN_ERROR_AUTHENTICATING, |
| HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT); |
| |
| if (login_status_consumer_) |
| login_status_consumer_->OnLoginFailure(error); |
| } |
| |
| void ScreenLocker::OnLoginSuccess(const UserContext& user_context) { |
| incorrect_passwords_count_ = 0; |
| if (authentication_start_time_.is_null()) { |
| if (!user_context.username.empty()) |
| LOG(ERROR) << "Start time is not set at authentication success"; |
| } else { |
| base::TimeDelta delta = base::Time::Now() - authentication_start_time_; |
| VLOG(1) << "Authentication success: " << delta.InSecondsF() << " second(s)"; |
| UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationSuccessTime", delta); |
| } |
| |
| if (const User* user = UserManager::Get()->FindUser(user_context.username)) { |
| if (!user->is_active()) |
| UserManager::Get()->SwitchActiveUser(user_context.username); |
| } else { |
| NOTREACHED() << "Logged in user not found."; |
| } |
| |
| authentication_capture_.reset(new AuthenticationParametersCapture()); |
| authentication_capture_->user_context = user_context; |
| |
| // Add guard for case when something get broken in call chain to unlock |
| // for sure. |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&ScreenLocker::UnlockOnLoginSuccess, |
| weak_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kUnlockGuardTimeoutMs)); |
| delegate_->AnimateAuthenticationSuccess(); |
| } |
| |
| void ScreenLocker::UnlockOnLoginSuccess() { |
| DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI); |
| if (!authentication_capture_.get()) { |
| LOG(WARNING) << "Call to UnlockOnLoginSuccess without previous " << |
| "authentication success."; |
| return; |
| } |
| |
| if (login_status_consumer_) { |
| login_status_consumer_->OnLoginSuccess( |
| UserContext(authentication_capture_->user_context.username, |
| authentication_capture_->user_context.password, |
| authentication_capture_->user_context.auth_code, |
| authentication_capture_->user_context.username_hash, |
| authentication_capture_->user_context.using_oauth)); |
| } |
| authentication_capture_.reset(); |
| weak_factory_.InvalidateWeakPtrs(); |
| |
| VLOG(1) << "Hiding the lock screen."; |
| chromeos::ScreenLocker::Hide(); |
| } |
| |
| void ScreenLocker::Authenticate(const UserContext& user_context) { |
| LOG_ASSERT(IsUserLoggedIn(user_context.username)) |
| << "Invalid user trying to unlock."; |
| authentication_start_time_ = base::Time::Now(); |
| delegate_->SetInputEnabled(false); |
| delegate_->OnAuthenticate(); |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&Authenticator::AuthenticateToUnlock, |
| authenticator_.get(), |
| user_context)); |
| } |
| |
| void ScreenLocker::AuthenticateByPassword(const std::string& password) { |
| LOG_ASSERT(users_.size() == 1); |
| Authenticate(UserContext(users_[0]->email(), password, "")); |
| } |
| |
| void ScreenLocker::ClearErrors() { |
| delegate_->ClearErrors(); |
| } |
| |
| void ScreenLocker::EnableInput() { |
| delegate_->SetInputEnabled(true); |
| } |
| |
| void ScreenLocker::Signout() { |
| delegate_->ClearErrors(); |
| content::RecordAction(UserMetricsAction("ScreenLocker_Signout")); |
| // We expect that this call will not wait for any user input. |
| // If it changes at some point, we will need to force exit. |
| chrome::AttemptUserExit(); |
| |
| // Don't hide yet the locker because the chrome screen may become visible |
| // briefly. |
| } |
| |
| void ScreenLocker::ShowBannerMessage(const std::string& message) { |
| delegate_->ShowBannerMessage(message); |
| } |
| |
| void ScreenLocker::ShowUserPodButton(const std::string& username, |
| const gfx::Image& icon, |
| const base::Closure& click_callback) { |
| if (!locked_) |
| return; |
| |
| screenlock_icon_provider_->AddIcon(username, icon); |
| delegate_->ShowUserPodButton( |
| username, |
| ScreenlockIconSource::GetIconURLForUser(username), |
| click_callback); |
| } |
| |
| void ScreenLocker::ShowErrorMessage(int error_msg_id, |
| HelpAppLauncher::HelpTopic help_topic_id, |
| bool sign_out_only) { |
| delegate_->SetInputEnabled(!sign_out_only); |
| delegate_->ShowErrorMessage(error_msg_id, help_topic_id); |
| } |
| |
| void ScreenLocker::SetLoginStatusConsumer( |
| chromeos::LoginStatusConsumer* consumer) { |
| login_status_consumer_ = consumer; |
| } |
| |
| // static |
| void ScreenLocker::Show() { |
| content::RecordAction(UserMetricsAction("ScreenLocker_Show")); |
| DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI); |
| |
| // Check whether the currently logged in user is a guest account and if so, |
| // refuse to lock the screen (crosbug.com/23764). |
| // For a demo user, we should never show the lock screen (crosbug.com/27647). |
| if (UserManager::Get()->IsLoggedInAsGuest() || |
| UserManager::Get()->IsLoggedInAsDemoUser()) { |
| VLOG(1) << "Refusing to lock screen for guest/demo account"; |
| return; |
| } |
| |
| // If the active window is fullscreen, exit fullscreen to avoid the web page |
| // or app mimicking the lock screen. Do not exit fullscreen if the shelf is |
| // visible while in fullscreen because the shelf makes it harder for a web |
| // page or app to mimick the lock screen. |
| ash::wm::WindowState* active_window_state = ash::wm::GetActiveWindowState(); |
| if (active_window_state && |
| active_window_state->IsFullscreen() && |
| active_window_state->hide_shelf_when_fullscreen()) { |
| active_window_state->ToggleFullscreen(); |
| } |
| |
| if (!screen_locker_) { |
| ScreenLocker* locker = |
| new ScreenLocker(UserManager::Get()->GetUnlockUsers()); |
| VLOG(1) << "Created ScreenLocker " << locker; |
| locker->Init(); |
| } else { |
| VLOG(1) << "ScreenLocker " << screen_locker_ << " already exists; " |
| << " calling session manager's HandleLockScreenShown D-Bus method"; |
| DBusThreadManager::Get()->GetSessionManagerClient()-> |
| NotifyLockScreenShown(); |
| } |
| } |
| |
| // static |
| void ScreenLocker::Hide() { |
| DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI); |
| // For a guest/demo user, screen_locker_ would have never been initialized. |
| if (UserManager::Get()->IsLoggedInAsGuest() || |
| UserManager::Get()->IsLoggedInAsDemoUser()) { |
| VLOG(1) << "Refusing to hide lock screen for guest/demo account"; |
| return; |
| } |
| |
| DCHECK(screen_locker_); |
| base::Callback<void(void)> callback = |
| base::Bind(&ScreenLocker::ScheduleDeletion); |
| ash::Shell::GetInstance()->lock_state_controller()-> |
| OnLockScreenHide(callback); |
| } |
| |
| void ScreenLocker::ScheduleDeletion() { |
| // Avoid possible multiple calls. |
| if (screen_locker_ == NULL) |
| return; |
| VLOG(1) << "Deleting ScreenLocker " << screen_locker_; |
| |
| PlaySound(SOUND_UNLOCK); |
| |
| delete screen_locker_; |
| screen_locker_ = NULL; |
| } |
| |
| // static |
| void ScreenLocker::InitClass() { |
| g_screen_lock_observer.Get(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ScreenLocker, private: |
| |
| ScreenLocker::~ScreenLocker() { |
| VLOG(1) << "Destroying ScreenLocker " << this; |
| DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI); |
| |
| if (authenticator_.get()) |
| authenticator_->SetConsumer(NULL); |
| ClearErrors(); |
| |
| VLOG(1) << "Moving desktop background to unlocked container"; |
| ash::Shell::GetInstance()-> |
| desktop_background_controller()->MoveDesktopToUnlockedContainer(); |
| |
| screen_locker_ = NULL; |
| bool state = false; |
| VLOG(1) << "Emitting SCREEN_LOCK_STATE_CHANGED with state=" << state; |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED, |
| content::Source<ScreenLocker>(this), |
| content::Details<bool>(&state)); |
| VLOG(1) << "Calling session manager's HandleLockScreenDismissed D-Bus method"; |
| DBusThreadManager::Get()->GetSessionManagerClient()-> |
| NotifyLockScreenDismissed(); |
| } |
| |
| void ScreenLocker::SetAuthenticator(Authenticator* authenticator) { |
| authenticator_ = authenticator; |
| } |
| |
| void ScreenLocker::ScreenLockReady() { |
| locked_ = true; |
| base::TimeDelta delta = base::Time::Now() - start_time_; |
| VLOG(1) << "ScreenLocker " << this << " is ready after " |
| << delta.InSecondsF() << " second(s)"; |
| UMA_HISTOGRAM_TIMES("ScreenLocker.ScreenLockTime", delta); |
| |
| VLOG(1) << "Moving desktop background to locked container"; |
| ash::Shell::GetInstance()-> |
| desktop_background_controller()->MoveDesktopToLockedContainer(); |
| |
| bool state = true; |
| VLOG(1) << "Emitting SCREEN_LOCK_STATE_CHANGED with state=" << state; |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED, |
| content::Source<ScreenLocker>(this), |
| content::Details<bool>(&state)); |
| VLOG(1) << "Calling session manager's HandleLockScreenShown D-Bus method"; |
| DBusThreadManager::Get()->GetSessionManagerClient()->NotifyLockScreenShown(); |
| } |
| |
| content::WebUI* ScreenLocker::GetAssociatedWebUI() { |
| return delegate_->GetAssociatedWebUI(); |
| } |
| |
| bool ScreenLocker::IsUserLoggedIn(const std::string& username) { |
| for (UserList::const_iterator it = users_.begin(); it != users_.end(); ++it) { |
| if ((*it)->email() == username) |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace chromeos |