| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/login/ui/local_authentication_request_controller_impl.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "ash/auth/views/active_session_auth_view.h" |
| #include "ash/auth/views/auth_common.h" |
| #include "ash/auth/views/auth_view_utils.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/login/ui/local_authentication_request_view.h" |
| #include "ash/login/ui/local_authentication_request_widget.h" |
| #include "ash/public/cpp/auth/active_session_auth_controller.h" |
| #include "ash/public/cpp/login_types.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/notreached.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/time.h" |
| #include "chromeos/ash/components/cryptohome/auth_factor_conversions.h" |
| #include "chromeos/ash/components/cryptohome/constants.h" |
| #include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h" |
| #include "chromeos/ash/components/login/auth/auth_performer.h" |
| #include "chromeos/ash/components/login/auth/public/auth_callbacks.h" |
| #include "chromeos/ash/components/login/auth/public/auth_session_intent.h" |
| #include "chromeos/ash/components/login/auth/public/session_auth_factors.h" |
| #include "chromeos/ash/components/login/auth/public/user_context.h" |
| #include "chromeos/ash/components/osauth/impl/auth_surface_registry.h" |
| #include "chromeos/ash/components/osauth/public/auth_session_storage.h" |
| #include "components/account_id/account_id.h" |
| #include "components/session_manager/session_manager_types.h" |
| #include "components/user_manager/known_user.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_delegate.h" |
| |
| // Enable VLOG level 1. |
| #undef ENABLED_VLOG_LEVEL |
| #define ENABLED_VLOG_LEVEL 1 |
| |
| namespace ash { |
| |
| LocalAuthenticationRequestControllerImpl:: |
| LocalAuthenticationRequestControllerImpl() = default; |
| |
| LocalAuthenticationRequestControllerImpl:: |
| ~LocalAuthenticationRequestControllerImpl() = default; |
| |
| void LocalAuthenticationRequestControllerImpl::OnClose() {} |
| |
| bool LocalAuthenticationRequestControllerImpl::ShowWidget( |
| LocalAuthenticationCallback local_authentication_callback, |
| std::unique_ptr<UserContext> user_context) { |
| if (LocalAuthenticationRequestWidget::Get()) { |
| LOG(ERROR) << "LocalAuthenticationRequestWidget is already shown."; |
| return false; |
| } |
| |
| const auto& auth_factors = user_context->GetAuthFactorsData(); |
| const cryptohome::AuthFactor* local_password_factor = |
| auth_factors.FindLocalPasswordFactor(); |
| if (local_password_factor == nullptr) { |
| LOG(ERROR) << "The local password authentication factor is not available, " |
| "skip to show the local authentication dialog."; |
| // TODO(b/334215182): It seems sometimes this dialog appears even when the |
| // local password is not available. |
| base::debug::DumpWithoutCrashing(); |
| return false; |
| } |
| |
| const AccountId& account_id = user_context->GetAccountId(); |
| |
| const std::string& user_email = account_id.GetUserEmail(); |
| |
| const std::u16string desc = l10n_util::GetStringFUTF16( |
| IDS_ASH_LOGIN_LOCAL_AUTHENTICATION_REQUEST_DESCRIPTION, |
| base::UTF8ToUTF16(user_email)); |
| |
| LocalAuthenticationRequestWidget::Show( |
| std::move(local_authentication_callback), |
| l10n_util::GetStringUTF16( |
| IDS_ASH_LOGIN_LOCAL_AUTHENTICATION_REQUEST_TITLE), |
| desc, weak_factory_.GetWeakPtr(), std::move(user_context)); |
| return true; |
| } |
| |
| bool LocalAuthenticationRequestControllerImpl::IsDialogVisible() const { |
| return LocalAuthenticationRequestWidget::Get() != nullptr; |
| } |
| |
| bool LocalAuthenticationRequestControllerImpl::IsPinSupported() const { |
| return false; |
| } |
| |
| namespace { |
| |
| // Read the salt from local state. |
| std::string GetUserSalt(const AccountId& account_id) { |
| user_manager::KnownUser known_user(Shell::Get()->local_state()); |
| if (const std::string* salt = |
| known_user.FindStringPath(account_id, prefs::kQuickUnlockPinSalt)) { |
| return *salt; |
| } |
| return {}; |
| } |
| |
| std::unique_ptr<views::Widget> CreateAuthDialogWidget( |
| std::unique_ptr<views::View> contents_view) { |
| views::Widget::InitParams params( |
| views::Widget::InitParams::CLIENT_OWNS_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; |
| params.delegate = new views::WidgetDelegate(); |
| params.show_state = ui::mojom::WindowShowState::kNormal; |
| |
| ShellWindowId parent_window_id = |
| Shell::Get()->session_controller()->GetSessionState() == |
| session_manager::SessionState::ACTIVE |
| ? kShellWindowId_SystemModalContainer |
| : kShellWindowId_LockSystemModalContainer; |
| params.parent = Shell::GetPrimaryRootWindow()->GetChildById(parent_window_id); |
| |
| params.autosize = true; |
| params.name = "AuthDialogWidget"; |
| |
| params.delegate->SetInitiallyFocusedView(contents_view.get()); |
| // ModalType::kSystem is used to get a semi-transparent background behind the |
| // local authentication request view, when it is used directly on a widget. |
| // The overlay consumes all the inputs from the user, so that they can only |
| // interact with the local authentication request view while it is visible. |
| params.delegate->SetModalType(ui::mojom::ModalType::kSystem); |
| params.delegate->SetOwnedByWidget(true); |
| |
| auto widget = std::make_unique<views::Widget>(); |
| widget->Init(std::move(params)); |
| widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_NONE); |
| widget->SetContentsView(std::move(contents_view)); |
| return widget; |
| } |
| |
| const char* LocalAuthenticationWithPinStateToString( |
| LocalAuthenticationWithPinControllerImpl::LocalAuthenticationWithPinState |
| state) { |
| switch (state) { |
| case LocalAuthenticationWithPinControllerImpl:: |
| LocalAuthenticationWithPinState::kWaitForInit: |
| return "WaitForInit"; |
| case LocalAuthenticationWithPinControllerImpl:: |
| LocalAuthenticationWithPinState::kInitialized: |
| return "Initialized"; |
| case LocalAuthenticationWithPinControllerImpl:: |
| LocalAuthenticationWithPinState::kPasswordAuthStarted: |
| return "PasswordAuthStarted"; |
| case LocalAuthenticationWithPinControllerImpl:: |
| LocalAuthenticationWithPinState::kPasswordAuthSucceeded: |
| return "PasswordAuthSucceeded"; |
| case LocalAuthenticationWithPinControllerImpl:: |
| LocalAuthenticationWithPinState::kPinAuthStarted: |
| return "PinAuthStarted"; |
| case LocalAuthenticationWithPinControllerImpl:: |
| LocalAuthenticationWithPinState::kPinAuthSucceeded: |
| return "PinAuthSucceeded"; |
| case LocalAuthenticationWithPinControllerImpl:: |
| LocalAuthenticationWithPinState::kCloseRequested: |
| return "CloseRequested"; |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| LocalAuthenticationWithPinControllerImpl:: |
| LocalAuthenticationWithPinControllerImpl() = default; |
| LocalAuthenticationWithPinControllerImpl:: |
| ~LocalAuthenticationWithPinControllerImpl() = default; |
| |
| bool LocalAuthenticationWithPinControllerImpl::IsSucceedState() const { |
| return state_ == LocalAuthenticationWithPinState::kPasswordAuthSucceeded || |
| state_ == LocalAuthenticationWithPinState::kPinAuthSucceeded; |
| } |
| |
| bool LocalAuthenticationWithPinControllerImpl::ShowWidget( |
| LocalAuthenticationCallback local_authentication_callback, |
| std::unique_ptr<UserContext> user_context) { |
| CHECK(user_context); |
| |
| if (widget_ != nullptr) { |
| LOG(ERROR) << "LocalAuthenticationWithPinController widget already exists."; |
| std::move(local_authentication_callback) |
| .Run(false, std::move(user_context)); |
| return false; |
| } |
| |
| title_ = l10n_util::GetStringUTF16( |
| IDS_ASH_LOGIN_LOCAL_AUTHENTICATION_REQUEST_TITLE); |
| |
| account_id_ = user_context->GetAccountId(); |
| |
| description_ = l10n_util::GetStringFUTF16( |
| IDS_ASH_LOGIN_LOCAL_AUTHENTICATION_REQUEST_DESCRIPTION, |
| base::UTF8ToUTF16(account_id_.GetUserEmail())); |
| |
| auth_performer_ = std::make_unique<AuthPerformer>(UserDataAuthClient::Get()); |
| |
| user_context_ = std::move(user_context); |
| local_authentication_callback_ = std::move(local_authentication_callback); |
| auth_session_broadcast_id_ = user_context_->GetBroadcastId(); |
| |
| UserDataAuthClient::Get()->AddAuthFactorStatusUpdateObserver(this); |
| |
| available_factors_.Clear(); |
| const auto& auth_factors = user_context_->GetAuthFactorsData(); |
| |
| if (auth_factors.FindAnyPasswordFactor()) { |
| available_factors_.Put(AuthInputType::kPassword); |
| } |
| |
| auto* pin_factor = auth_factors.FindPinFactor(); |
| if (pin_factor) { |
| if (!pin_factor->GetPinStatus().IsLockedFactor()) { |
| available_factors_.Put(AuthInputType::kPin); |
| } |
| } |
| |
| auto contents_view = std::make_unique<ActiveSessionAuthView>( |
| account_id_, title_, description_, available_factors_); |
| contents_view_ = contents_view.get(); |
| |
| widget_ = CreateAuthDialogWidget(std::move(contents_view)); |
| contents_view_observer_.Observe(contents_view_); |
| contents_view_->AddObserver(this); |
| SetState(LocalAuthenticationWithPinState::kInitialized); |
| |
| if (pin_factor) { |
| contents_view_->SetPinStatus( |
| std::make_unique<cryptohome::PinStatus>(pin_factor->GetPinStatus())); |
| } |
| |
| MoveToTheCenter(); |
| widget_->Show(); |
| |
| return true; |
| } |
| |
| bool LocalAuthenticationWithPinControllerImpl::IsDialogVisible() const { |
| return state_ != LocalAuthenticationWithPinState::kWaitForInit; |
| } |
| |
| bool LocalAuthenticationWithPinControllerImpl::IsPinSupported() const { |
| return true; |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::StartClose() { |
| VLOG(1) << "Close with : " << LocalAuthenticationWithPinStateToString(state_) |
| << " state."; |
| |
| CHECK(user_context_); |
| CHECK(auth_performer_); |
| contents_view_observer_.Reset(); |
| CHECK(contents_view_); |
| contents_view_->RemoveObserver(this); |
| contents_view_ = nullptr; |
| auth_session_broadcast_id_.clear(); |
| |
| UserDataAuthClient::Get()->RemoveAuthFactorStatusUpdateObserver(this); |
| |
| auth_performer_->InvalidateCurrentAttempts(); |
| auth_performer_.reset(); |
| |
| available_factors_.Clear(); |
| |
| bool success = IsSucceedState(); |
| |
| SetState(LocalAuthenticationWithPinState::kWaitForInit); |
| |
| title_.clear(); |
| description_.clear(); |
| widget_.reset(); |
| |
| std::move(local_authentication_callback_) |
| .Run(success, std::move(user_context_)); |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::OnViewPreferredSizeChanged( |
| views::View* observed_view) { |
| MoveToTheCenter(); |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::MoveToTheCenter() { |
| widget_->CenterWindow(widget_->GetContentsView()->GetPreferredSize()); |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::OnPasswordSubmit( |
| std::u16string_view password) { |
| if (IsSucceedState()) { |
| return; |
| } |
| |
| CHECK_EQ(state_, LocalAuthenticationWithPinState::kInitialized); |
| |
| std::string password_utf8; |
| bool conversion_succeeded = |
| base::UTF16ToUTF8(password.data(), password.length(), &password_utf8); |
| |
| if (!conversion_succeeded) { |
| contents_view_->SetErrorTitle( |
| l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PASSWORD_INVALID)); |
| return; |
| } |
| |
| SetState(LocalAuthenticationWithPinState::kPasswordAuthStarted); |
| CHECK(user_context_); |
| const auto* password_factor = |
| user_context_->GetAuthFactorsData().FindAnyPasswordFactor(); |
| CHECK(password_factor); |
| |
| const cryptohome::KeyLabel key_label = password_factor->ref().label(); |
| |
| auth_performer_->AuthenticateWithPassword( |
| key_label.value(), password_utf8, std::move(user_context_), |
| base::BindOnce(&LocalAuthenticationWithPinControllerImpl::OnAuthComplete, |
| weak_ptr_factory_.GetWeakPtr(), AuthInputType::kPassword)); |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::OnPinSubmit( |
| std::u16string_view pin) { |
| if (IsSucceedState()) { |
| return; |
| } |
| |
| CHECK_EQ(state_, LocalAuthenticationWithPinState::kInitialized); |
| |
| std::string pin_utf8; |
| bool conversion_succeeded = |
| base::UTF16ToUTF8(pin.data(), pin.length(), &pin_utf8); |
| |
| if (!conversion_succeeded) { |
| contents_view_->SetErrorTitle( |
| l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PIN_INVALID)); |
| return; |
| } |
| |
| SetState(LocalAuthenticationWithPinState::kPinAuthStarted); |
| CHECK(user_context_); |
| const std::string salt = GetUserSalt(account_id_); |
| |
| auth_performer_->AuthenticateWithPin( |
| pin_utf8, salt, std::move(user_context_), |
| base::BindOnce(&LocalAuthenticationWithPinControllerImpl::OnAuthComplete, |
| weak_ptr_factory_.GetWeakPtr(), AuthInputType::kPin)); |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::OnAuthComplete( |
| AuthInputType input_type, |
| std::unique_ptr<UserContext> user_context, |
| std::optional<AuthenticationError> authentication_error) { |
| user_context_ = std::move(user_context); |
| CHECK(!IsSucceedState()); |
| if (state_ == LocalAuthenticationWithPinState::kCloseRequested) { |
| StartClose(); |
| return; |
| } |
| if (authentication_error.has_value()) { |
| contents_view_->SetErrorTitle(l10n_util::GetStringUTF16( |
| input_type == AuthInputType::kPassword |
| ? IDS_ASH_IN_SESSION_AUTH_PASSWORD_INCORRECT |
| : IDS_ASH_IN_SESSION_AUTH_PIN_INCORRECT)); |
| SetState(LocalAuthenticationWithPinState::kInitialized); |
| } else { |
| SetState(input_type == AuthInputType::kPassword |
| ? LocalAuthenticationWithPinState::kPasswordAuthSucceeded |
| : LocalAuthenticationWithPinState::kPinAuthSucceeded); |
| StartClose(); |
| } |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::OnClose() { |
| switch (state_) { |
| case LocalAuthenticationWithPinState::kWaitForInit: |
| NOTREACHED(); |
| case LocalAuthenticationWithPinState::kInitialized: |
| StartClose(); |
| return; |
| case LocalAuthenticationWithPinState::kPasswordAuthStarted: |
| case LocalAuthenticationWithPinState::kPinAuthStarted: |
| SetState(LocalAuthenticationWithPinState::kCloseRequested); |
| return; |
| case LocalAuthenticationWithPinState::kPasswordAuthSucceeded: |
| case LocalAuthenticationWithPinState::kPinAuthSucceeded: |
| case LocalAuthenticationWithPinState::kCloseRequested: |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::SetState( |
| LocalAuthenticationWithPinState state) { |
| VLOG(1) << "SetState is requested from: " |
| << LocalAuthenticationWithPinStateToString(state_) |
| << " state to : " << LocalAuthenticationWithPinStateToString(state) |
| << " state."; |
| switch (state) { |
| case LocalAuthenticationWithPinState::kWaitForInit: |
| break; |
| case LocalAuthenticationWithPinState::kInitialized: |
| CHECK(state_ == LocalAuthenticationWithPinState::kWaitForInit || |
| state_ == LocalAuthenticationWithPinState::kPasswordAuthStarted || |
| state_ == LocalAuthenticationWithPinState::kPinAuthStarted); |
| contents_view_->SetInputEnabled(true); |
| break; |
| case LocalAuthenticationWithPinState::kPasswordAuthStarted: |
| // Disable the UI while we are waiting for the response, except the close |
| // button. |
| CHECK_EQ(state_, LocalAuthenticationWithPinState::kInitialized); |
| contents_view_->SetInputEnabled(false); |
| break; |
| case LocalAuthenticationWithPinState::kPasswordAuthSucceeded: |
| CHECK_EQ(state_, LocalAuthenticationWithPinState::kPasswordAuthStarted); |
| break; |
| case LocalAuthenticationWithPinState::kPinAuthStarted: |
| CHECK_EQ(state_, LocalAuthenticationWithPinState::kInitialized); |
| contents_view_->SetInputEnabled(false); |
| break; |
| case LocalAuthenticationWithPinState::kPinAuthSucceeded: |
| CHECK_EQ(state_, LocalAuthenticationWithPinState::kPinAuthStarted); |
| break; |
| case LocalAuthenticationWithPinState::kCloseRequested: |
| CHECK(state_ == LocalAuthenticationWithPinState::kPasswordAuthStarted || |
| state_ == LocalAuthenticationWithPinState::kPinAuthStarted); |
| contents_view_->SetInputEnabled(false); |
| break; |
| } |
| state_ = state; |
| } |
| |
| void LocalAuthenticationWithPinControllerImpl::OnAuthFactorStatusUpdate( |
| const user_data_auth::AuthFactorStatusUpdate& update) { |
| switch (state_) { |
| case LocalAuthenticationWithPinState::kInitialized: |
| case LocalAuthenticationWithPinState::kPinAuthStarted: |
| case LocalAuthenticationWithPinState::kPasswordAuthStarted: |
| CHECK_NE(auth_session_broadcast_id_, ""); |
| if (auth_session_broadcast_id_ == update.broadcast_id()) { |
| auto auth_factor = cryptohome::DeserializeAuthFactor( |
| update.auth_factor_with_status(), |
| /*fallback_type=*/cryptohome::AuthFactorType::kPassword); |
| if (auth_factor.ref().type() == cryptohome::AuthFactorType::kPin) { |
| auto pin_status = auth_factor.GetPinStatus(); |
| contents_view_->SetPinStatus( |
| std::make_unique<cryptohome::PinStatus>(pin_status)); |
| } |
| } |
| return; |
| |
| case LocalAuthenticationWithPinState::kWaitForInit: |
| return; |
| |
| case LocalAuthenticationWithPinState::kPasswordAuthSucceeded: |
| case LocalAuthenticationWithPinState::kPinAuthSucceeded: |
| case LocalAuthenticationWithPinState::kCloseRequested: |
| // No need to handle PIN updates as dialog closing is in progress. |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace ash |