0

Add Local Authentication Dialog for re-auth session.

- Implement LocalAuthenticationdRequestView and related Widget and Controller
- Add unittest
- Add pixeltest
- Add a button that can use the dialog with a debug view

Change-Id: I2301ca4eea6ad21cb07a7f22a94de3af9e2ba748
Bug: b:291811391
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4819967
Reviewed-by: Denis Kuznetsov <antrim@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Istvan Nagy <iscsi@google.com>
Cr-Commit-Position: refs/heads/main@{#1217272}
This commit is contained in:
Istvan Nagy
2023-10-30 23:32:17 +00:00
committed by Chromium LUCI CQ
parent 533025a949
commit 93bfdf55e7
19 changed files with 1570 additions and 3 deletions

@ -794,6 +794,12 @@ component("ash") {
"login/ui/image_parser.h",
"login/ui/kiosk_app_default_message.cc",
"login/ui/kiosk_app_default_message.h",
"login/ui/local_authentication_request_controller_impl.cc",
"login/ui/local_authentication_request_controller_impl.h",
"login/ui/local_authentication_request_view.cc",
"login/ui/local_authentication_request_view.h",
"login/ui/local_authentication_request_widget.cc",
"login/ui/local_authentication_request_widget.h",
"login/ui/lock_contents_view.cc",
"login/ui/lock_contents_view.h",
"login/ui/lock_contents_view_constants.h",
@ -2842,6 +2848,8 @@ component("ash") {
"//chromeos/ash/components/assistant:buildflags",
"//chromeos/ash/components/audio",
"//chromeos/ash/components/auth_panel",
"//chromeos/ash/components/cryptohome:cryptohome",
"//chromeos/ash/components/cryptohome:public",
"//chromeos/ash/components/dbus/audio",
"//chromeos/ash/components/dbus/biod",
"//chromeos/ash/components/dbus/dlcservice",
@ -2860,6 +2868,7 @@ component("ash") {
"//chromeos/ash/components/dbus/typecd",
"//chromeos/ash/components/dbus/update_engine",
"//chromeos/ash/components/dbus/usb",
"//chromeos/ash/components/dbus/userdataauth:userdataauth",
"//chromeos/ash/components/early_prefs",
"//chromeos/ash/components/feature_usage",
"//chromeos/ash/components/fwupd",
@ -3291,8 +3300,6 @@ test("ash_unittests") {
"lock_screen_action/lock_screen_note_display_state_handler_unittest.cc",
"lock_screen_action/lock_screen_note_launcher_unittest.cc",
"login/login_screen_controller_unittest.cc",
"login/mock_login_screen_client.cc",
"login/mock_login_screen_client.h",
"login/security_token_request_controller_unittest.cc",
"login/ui/access_code_input.cc",
"login/ui/access_code_input.h",
@ -3305,6 +3312,7 @@ test("ash_unittests") {
"login/ui/fake_smart_lock_auth_factor_model.cc",
"login/ui/fake_smart_lock_auth_factor_model.h",
"login/ui/fingerprint_auth_factor_model_unittest.cc",
"login/ui/local_authentication_request_controller_impl_unittest.cc",
"login/ui/lock_contents_view_unittest.cc",
"login/ui/lock_screen_media_controls_view_unittest.cc",
"login/ui/lock_screen_media_view_unittest.cc",
@ -3873,8 +3881,11 @@ test("ash_unittests") {
"//chromeos/ash/components/assistant:buildflags",
"//chromeos/ash/components/audio",
"//chromeos/ash/components/cryptohome",
"//chromeos/ash/components/cryptohome:cryptohome",
"//chromeos/ash/components/dbus:test_support",
"//chromeos/ash/components/dbus/audio",
"//chromeos/ash/components/dbus/cryptohome:cryptohome",
"//chromeos/ash/components/dbus/cryptohome:cryptohome_proto",
"//chromeos/ash/components/dbus/dlcservice",
"//chromeos/ash/components/dbus/dlcservice:dlcservice_proto",
"//chromeos/ash/components/dbus/fwupd",
@ -4066,6 +4077,7 @@ test("ash_pixeltests") {
"glanceables/glanceables_pixeltest.cc",
"glanceables/tasks/glanceables_task_view_pixeltest.cc",
"in_session_auth/auth_dialog_contents_view_pixeltest.cc",
"login/ui/local_authentication_request_controller_impl_pixeltest.cc",
"shelf/login_shelf_view_pixeltest.cc",
"shelf/scrollable_shelf_view_pixeltest.cc",
"shelf/shelf_layout_manager_pixeltest.cc",
@ -4115,7 +4127,11 @@ test("ash_pixeltests") {
"//ash/public/cpp:test_support",
"//base/test:test_support",
"//chromeos/ash/components/audio",
"//chromeos/ash/components/cryptohome:cryptohome",
"//chromeos/ash/components/dbus/audio",
"//chromeos/ash/components/dbus/cryptohome:cryptohome",
"//chromeos/ash/components/dbus/cryptohome:cryptohome_proto",
"//chromeos/ash/components/dbus/userdataauth",
"//chromeos/ash/services/assistant/public/cpp",
"//chromeos/ash/services/bluetooth_config:test_support",
"//chromeos/dbus/power:power",
@ -4123,6 +4139,8 @@ test("ash_pixeltests") {
"//chromeos/services/network_config/public/cpp:test_support",
"//components/user_education/common",
"//components/user_education/views",
"//components/user_manager",
"//components/user_manager:test_support",
"//components/viz/test:test_support",
"//google_apis/calendar:test_support",
"//mojo/core/embedder:embedder",
@ -4291,6 +4309,8 @@ static_library("test_support") {
"lock_screen_action/test_lock_screen_action_background_controller.cc",
"lock_screen_action/test_lock_screen_action_background_controller.h",
"login/login_screen_test_api.cc",
"login/mock_login_screen_client.cc",
"login/mock_login_screen_client.h",
"login/ui/login_test_base.cc",
"login/ui/login_test_base.h",
"login/ui/login_test_utils.cc",

@ -4800,6 +4800,12 @@ Some features are limited to increase battery life.
<message name="IDS_ASH_LOGIN_PIN_REQUEST_NEXT_NUMBER_PROMPT" desc="Accessible prompt read when next access code input field has been focused. Asks user to enter next piece of the access code.">
Next number
</message>
<message name="IDS_ASH_LOGIN_LOCAL_AUTHENTICATION_REQUEST_TITLE" desc="Title of the local authentication input dialog that allows the user to login during the reauthenticate session.">
Finish verifying your identity
</message>
<message name="IDS_ASH_LOGIN_LOCAL_AUTHENTICATION_REQUEST_DESCRIPTION" desc="Description shown on the local authentication input dialog. Explains local authentication credentials should be used to unlock the device.">
Enter device password for <ph name="EMAIL">$1<ex>john.doe@example.com</ex></ph>
</message>
<message name="IDS_ASH_LOGIN_SECURITY_TOKEN_REQUEST_DIALOG_TITLE" desc="Title of the PIN dialog to unlock the device with a smart card.">
Smart card PIN
</message>

@ -0,0 +1 @@
5d68d9ee4e421920f0866383b06fb1fc6bbd6b3e

@ -0,0 +1 @@
ca2a3ba9a0c6a226f293dbb8943414468b65f553

@ -9,7 +9,6 @@
#include "ash/public/cpp/login_screen_client.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "components/password_manager/core/browser/hash_password_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace ash {

@ -0,0 +1,57 @@
// 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/login/ui/local_authentication_request_view.h"
#include "ash/login/ui/local_authentication_request_widget.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/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "components/account_id/account_id.h"
#include "components/session_manager/session_manager_types.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
LocalAuthenticationRequestControllerImpl::
LocalAuthenticationRequestControllerImpl() = default;
LocalAuthenticationRequestControllerImpl::
~LocalAuthenticationRequestControllerImpl() = default;
void LocalAuthenticationRequestControllerImpl::OnClose() {}
bool LocalAuthenticationRequestControllerImpl::ShowWidget(
OnLocalAuthenticationCompleted on_local_authentication_completed,
std::unique_ptr<UserContext> user_context) {
if (LocalAuthenticationRequestWidget::Get()) {
LOG(ERROR) << "LocalAuthenticationRequestWidget is already shown.";
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(on_local_authentication_completed),
l10n_util::GetStringUTF16(
IDS_ASH_LOGIN_LOCAL_AUTHENTICATION_REQUEST_TITLE),
desc, this, std::move(user_context));
return true;
}
} // namespace ash

@ -0,0 +1,47 @@
// 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.
#ifndef ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_CONTROLLER_IMPL_H_
#define ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_CONTROLLER_IMPL_H_
#include <memory>
#include "ash/login/ui/local_authentication_request_view.h"
#include "ash/login/ui/local_authentication_request_widget.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "base/memory/weak_ptr.h"
namespace ash {
class UserContext;
// Implementation of LocalAuthenticationRequestController. It serves to finalize
// the re-auth session with local authentication.
class ASH_EXPORT LocalAuthenticationRequestControllerImpl
: public LocalAuthenticationRequestController,
public LocalAuthenticationRequestView::Delegate {
public:
LocalAuthenticationRequestControllerImpl();
LocalAuthenticationRequestControllerImpl(
const LocalAuthenticationRequestControllerImpl&) = delete;
LocalAuthenticationRequestControllerImpl& operator=(
const LocalAuthenticationRequestControllerImpl&) = delete;
~LocalAuthenticationRequestControllerImpl() override;
// LocalAuthenticationRequestView::Delegate:
void OnClose() override;
// LocalAuthenticationRequestController:
bool ShowWidget(
OnLocalAuthenticationCompleted on_local_authentication_completed,
std::unique_ptr<UserContext> user_context) override;
private:
base::WeakPtrFactory<LocalAuthenticationRequestControllerImpl> weak_factory_{
this};
};
} // namespace ash
#endif // ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_CONTROLLER_IMPL_H_

@ -0,0 +1,253 @@
// 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 <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/login/ui/local_authentication_request_view.h"
#include "ash/login/ui/local_authentication_request_widget.h"
#include "ash/login/ui/login_button.h"
#include "ash/login/ui/login_test_base.h"
#include "ash/login/ui/views_utils.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "ash/shell.h"
#include "ash/style/dark_light_mode_controller_impl.h"
#include "ash/test/pixel/ash_pixel_differ.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
#include "chromeos/ash/components/dbus/cryptohome/account_identifier_operators.h"
#include "chromeos/ash/components/dbus/cryptohome/key.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/ash/components/login/auth/public/cryptohome_key_constants.h"
#include "chromeos/ash/components/login/auth/public/key.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "components/session_manager/session_manager_types.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "ui/chromeos/resources/grit/ui_chromeos_resources.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/views/test/button_test_api.h"
namespace ash {
namespace {
using ::cryptohome::KeyLabel;
const char kTestAccount[] = "user@test.com";
const char kExpectedPassword[] = "qwerty";
class LocalAuthenticationRequestControllerImplPixelTest : public AshTestBase {
public:
LocalAuthenticationRequestControllerImplPixelTest(
const LocalAuthenticationRequestControllerImplPixelTest&) = delete;
LocalAuthenticationRequestControllerImplPixelTest& operator=(
const LocalAuthenticationRequestControllerImplPixelTest&) = delete;
protected:
LocalAuthenticationRequestControllerImplPixelTest() = default;
~LocalAuthenticationRequestControllerImplPixelTest() override = default;
absl::optional<pixel_test::InitParams> CreatePixelTestInitParams()
const override {
return pixel_test::InitParams();
}
void SetUp() override {
AshTestBase::SetUp();
UpdateDisplay("600x800");
auto* dark_light_mode_controller = DarkLightModeControllerImpl::Get();
dark_light_mode_controller->SetAutoScheduleEnabled(false);
// Test Base should setup the dark mode.
EXPECT_EQ(dark_light_mode_controller->IsDarkModeEnabled(), true);
CryptohomeMiscClient::InitializeFake();
FakeCryptohomeMiscClient::Get()->SetServiceIsAvailable(true);
FakeCryptohomeMiscClient::Get()->set_system_salt(
FakeCryptohomeMiscClient::GetStubSystemSalt());
UserDataAuthClient::InitializeFake();
SystemSaltGetter::Initialize();
test_account_id_ = AccountId::FromUserEmail(kTestAccount);
SetExpectedCredentialsWithDbusClient(test_account_id_, kExpectedPassword);
auto fake_user_manager = std::make_unique<user_manager::FakeUserManager>();
fake_user_manager->AddUser(test_account_id_);
scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(fake_user_manager));
}
void TearDown() override {
// If the test did not explicitly dismissed the widget, destroy it now.
LocalAuthenticationRequestWidget* local_authentication_request_widget =
LocalAuthenticationRequestWidget::Get();
if (local_authentication_request_widget) {
local_authentication_request_widget->Close(
false /* validation success */);
}
scoped_user_manager_.reset();
SystemSaltGetter::Shutdown();
UserDataAuthClient::Shutdown();
CryptohomeMiscClient::Shutdown();
AshTestBase::TearDown();
}
void SetExpectedCredentialsWithDbusClient(const AccountId& account_id,
const std::string& password) {
auto* test_api = FakeUserDataAuthClient::TestApi::Get();
test_api->set_enable_auth_check(true);
const auto cryptohome_id =
cryptohome::CreateAccountIdentifierFromAccountId(account_id);
Key key{password};
key.Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
SystemSaltGetter::ConvertRawSaltToHexString(
FakeCryptohomeMiscClient::GetStubSystemSalt()));
cryptohome::Key cryptohome_key;
cryptohome_key.mutable_data()->set_label(kCryptohomeLocalPasswordKeyLabel);
cryptohome_key.set_secret(key.GetSecret());
test_api->AddExistingUser(cryptohome_id);
test_api->AddKey(cryptohome_id, cryptohome_key);
session_ids_ = test_api->AddSession(cryptohome_id, false);
}
// Simulates mouse press event on a |button|.
void SimulateButtonPress(views::Button* button) {
ui::MouseEvent event(/*type=*/ui::ET_MOUSE_PRESSED,
/*location=*/gfx::Point(),
/*root_location=*/gfx::Point(),
/*time_stamp=*/ui::EventTimeForNow(),
/*flags=*/0,
/*changed_button_flags=*/0);
views::test::ButtonTestApi(button).NotifyClick(event);
}
// Called when LocalAuthenticationRequestView finished processing.
void OnFinished(bool access_granted) {
access_granted ? ++successful_validation_ : ++close_action_;
}
// Starts local authentication validation.
void StartLocalAuthenticationRequest() {
// Configure the user context.
std::unique_ptr<UserContext> user_context = std::make_unique<UserContext>(
user_manager::USER_TYPE_REGULAR, test_account_id_);
user_context->SetAuthSessionIds(session_ids_.first, session_ids_.second);
// Add local password as an auth factor.
std::vector<cryptohome::AuthFactor> factors;
cryptohome::AuthFactorRef ref(cryptohome::AuthFactorType::kPassword,
KeyLabel(kCryptohomeLocalPasswordKeyLabel));
cryptohome::AuthFactor factor(ref, cryptohome::AuthFactorCommonMetadata());
factors.push_back(factor);
SessionAuthFactors data(factors);
user_context->SetSessionAuthFactors(data);
user_context->SetKey(Key(kExpectedPassword));
user_context->SetPasswordKey(Key(kExpectedPassword));
Shell::Get()->local_authentication_request_controller()->ShowWidget(
base::BindOnce(
&LocalAuthenticationRequestControllerImplPixelTest::OnFinished,
base::Unretained(this)),
std::move(user_context));
}
// Simulates entering a password. |success| determines whether the code will
// be accepted.
void SimulateValidation(bool success) {
// Submit password.
for (char c : kExpectedPassword) {
ui::KeyboardCode code =
static_cast<ui::KeyboardCode>(ui::KeyboardCode::VKEY_A + (c - 'a'));
PressAndReleaseKey(code);
}
if (!success) {
PressAndReleaseKey(ui::KeyboardCode::VKEY_A);
}
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN);
base::RunLoop().RunUntilIdle();
}
// Number of times the view was dismissed with close button.
int close_action_ = 0;
// Number of times the view was dismissed after successful validation.
int successful_validation_ = 0;
// Test account id.
AccountId test_account_id_;
// Auth session ids.
std::pair<std::string, std::string> session_ids_;
// Container object for the fake user manager for tests.
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
};
// Tests local authentication dialog showing/hiding and focus behavior for
// password field
TEST_F(LocalAuthenticationRequestControllerImplPixelTest, FailedValidation) {
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
StartLocalAuthenticationRequest();
LocalAuthenticationRequestView* view =
LocalAuthenticationRequestWidget::GetViewForTesting();
ASSERT_NE(view, nullptr);
LocalAuthenticationRequestView::TestApi view_test_api(view);
// Hide the textfield cursor to avoid the flakiness due to the blinking.
views::TextfieldTestApi(view_test_api.GetInputTextfield())
.SetCursorLayerOpacity(0.f);
ASSERT_TRUE(LocalAuthenticationRequestWidget::Get());
// Verify the UI.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
"Ready", /*revision_number=*/0, view));
SimulateValidation(false);
// Verify the UI.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
"Fail", /*revision_number=*/0, view));
}
// Tests local authentication dialog theme change
TEST_F(LocalAuthenticationRequestControllerImplPixelTest, ThemeChange) {
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
StartLocalAuthenticationRequest();
LocalAuthenticationRequestView* view =
LocalAuthenticationRequestWidget::GetViewForTesting();
ASSERT_NE(view, nullptr);
LocalAuthenticationRequestView::TestApi view_test_api(view);
// Hide the textfield cursor to avoid the flakiness due to the blinking.
views::TextfieldTestApi(view_test_api.GetInputTextfield())
.SetCursorLayerOpacity(0.f);
ASSERT_TRUE(LocalAuthenticationRequestWidget::Get());
DarkLightModeControllerImpl::Get()->SetDarkModeEnabledForTest(false);
// Verify the UI.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
"Light", /*revision_number=*/0, view));
}
} // namespace
} // namespace ash

@ -0,0 +1,269 @@
// 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 <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/login/ui/local_authentication_request_view.h"
#include "ash/login/ui/local_authentication_request_widget.h"
#include "ash/login/ui/login_button.h"
#include "ash/login/ui/login_test_base.h"
#include "ash/login/ui/views_utils.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
#include "chromeos/ash/components/dbus/cryptohome/account_identifier_operators.h"
#include "chromeos/ash/components/dbus/cryptohome/key.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/ash/components/login/auth/public/cryptohome_key_constants.h"
#include "chromeos/ash/components/login/auth/public/key.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "components/session_manager/session_manager_types.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/test/button_test_api.h"
namespace ash {
namespace {
using ::cryptohome::KeyLabel;
const char kTestAccount[] = "user@test.com";
const char kExpectedPassword[] = "qwerty";
class LocalAuthenticationRequestControllerImplTest : public LoginTestBase {
public:
LocalAuthenticationRequestControllerImplTest(
const LocalAuthenticationRequestControllerImplTest&) = delete;
LocalAuthenticationRequestControllerImplTest& operator=(
const LocalAuthenticationRequestControllerImplTest&) = delete;
protected:
LocalAuthenticationRequestControllerImplTest() = default;
~LocalAuthenticationRequestControllerImplTest() override = default;
// LoginScreenTest:
void SetUp() override {
LoginTestBase::SetUp();
CryptohomeMiscClient::InitializeFake();
FakeCryptohomeMiscClient::Get()->SetServiceIsAvailable(true);
FakeCryptohomeMiscClient::Get()->set_system_salt(
FakeCryptohomeMiscClient::GetStubSystemSalt());
UserDataAuthClient::InitializeFake();
SystemSaltGetter::Initialize();
test_account_id_ = AccountId::FromUserEmail(kTestAccount);
SetExpectedCredentialsWithDbusClient(test_account_id_, kExpectedPassword);
auto fake_user_manager = std::make_unique<user_manager::FakeUserManager>();
fake_user_manager->AddUser(test_account_id_);
scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(fake_user_manager));
}
void TearDown() override {
LoginTestBase::TearDown();
// If the test did not explicitly dismissed the widget, destroy it now.
LocalAuthenticationRequestWidget* local_authentication_request_widget =
LocalAuthenticationRequestWidget::Get();
if (local_authentication_request_widget) {
local_authentication_request_widget->Close(
false /* validation success */);
}
scoped_user_manager_.reset();
SystemSaltGetter::Shutdown();
UserDataAuthClient::Shutdown();
CryptohomeMiscClient::Shutdown();
}
void SetExpectedCredentialsWithDbusClient(const AccountId& account_id,
const std::string& password) {
auto* test_api = FakeUserDataAuthClient::TestApi::Get();
test_api->set_enable_auth_check(true);
const auto cryptohome_id =
cryptohome::CreateAccountIdentifierFromAccountId(account_id);
Key key{password};
key.Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
SystemSaltGetter::ConvertRawSaltToHexString(
FakeCryptohomeMiscClient::GetStubSystemSalt()));
cryptohome::Key cryptohome_key;
cryptohome_key.mutable_data()->set_label(kCryptohomeLocalPasswordKeyLabel);
cryptohome_key.set_secret(key.GetSecret());
test_api->AddExistingUser(cryptohome_id);
test_api->AddKey(cryptohome_id, cryptohome_key);
session_ids_ = test_api->AddSession(cryptohome_id, false);
}
// Simulates mouse press event on a |button|.
void SimulateButtonPress(views::Button* button) {
ui::MouseEvent event(/*type=*/ui::ET_MOUSE_PRESSED,
/*location=*/gfx::Point(),
/*root_location=*/gfx::Point(),
/*time_stamp=*/ui::EventTimeForNow(),
/*flags=*/0,
/*changed_button_flags=*/0);
views::test::ButtonTestApi(button).NotifyClick(event);
}
// Called when LocalAuthenticationRequestView finished processing.
void OnFinished(bool access_granted) {
access_granted ? ++successful_validation_ : ++close_action_;
}
// Starts local authentication validation.
void StartLocalAuthenticationRequest() {
// Configure the user context.
std::unique_ptr<UserContext> user_context = std::make_unique<UserContext>(
user_manager::USER_TYPE_REGULAR, test_account_id_);
user_context->SetAuthSessionIds(session_ids_.first, session_ids_.second);
// Add local password as an auth factor.
std::vector<cryptohome::AuthFactor> factors;
cryptohome::AuthFactorRef ref(cryptohome::AuthFactorType::kPassword,
KeyLabel(kCryptohomeLocalPasswordKeyLabel));
cryptohome::AuthFactor factor(ref, cryptohome::AuthFactorCommonMetadata());
factors.push_back(factor);
SessionAuthFactors data(factors);
user_context->SetSessionAuthFactors(data);
user_context->SetKey(Key(kExpectedPassword));
user_context->SetPasswordKey(Key(kExpectedPassword));
Shell::Get()->local_authentication_request_controller()->ShowWidget(
base::BindOnce(
&LocalAuthenticationRequestControllerImplTest::OnFinished,
base::Unretained(this)),
std::move(user_context));
}
// Simulates entering a password. |success| determines whether the code will
// be accepted.
void SimulateValidation(bool success) {
// Submit password.
for (char c : kExpectedPassword) {
ui::KeyboardCode code =
static_cast<ui::KeyboardCode>(ui::KeyboardCode::VKEY_A + (c - 'a'));
PressAndReleaseKey(code);
}
if (!success) {
PressAndReleaseKey(ui::KeyboardCode::VKEY_A);
}
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN);
base::RunLoop().RunUntilIdle();
}
// Number of times the view was dismissed with close button.
int close_action_ = 0;
// Number of times the view was dismissed after successful validation.
int successful_validation_ = 0;
// Test account id.
AccountId test_account_id_;
// Auth session ids.
std::pair<std::string, std::string> session_ids_;
// Container object for the fake user manager for tests.
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
};
// Tests local authentication dialog showing/hiding and focus behavior for
// password field
TEST_F(LocalAuthenticationRequestControllerImplTest,
LocalAuthenticationRequestDialogFocus) {
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
StartLocalAuthenticationRequest();
LocalAuthenticationRequestView* view =
LocalAuthenticationRequestWidget::GetViewForTesting();
ASSERT_NE(view, nullptr);
ASSERT_TRUE(LocalAuthenticationRequestWidget::Get());
LoginPasswordView* login_password_view =
LocalAuthenticationRequestView::TestApi(view).login_password_view();
EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(login_password_view));
LocalAuthenticationRequestWidget::Get()->Close(
false /* validation success */);
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
}
// Tests successful authentication flow.
TEST_F(LocalAuthenticationRequestControllerImplTest,
LocalAuthenticationRequestSuccessfulValidation) {
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
StartLocalAuthenticationRequest();
SimulateValidation(true);
EXPECT_EQ(1, successful_validation_);
EXPECT_EQ(0, close_action_);
// Widget closed after successful validation
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
}
// Tests failed authentication flow.
TEST_F(LocalAuthenticationRequestControllerImplTest,
LocalAuthenticationRequestFailedValidation) {
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
StartLocalAuthenticationRequest();
SimulateValidation(false);
EXPECT_EQ(0, successful_validation_);
EXPECT_EQ(0, close_action_);
// Widget still exists despite the auth error.
EXPECT_TRUE(LocalAuthenticationRequestWidget::Get());
LocalAuthenticationRequestView* view =
LocalAuthenticationRequestWidget::GetViewForTesting();
ASSERT_NE(view, nullptr);
LocalAuthenticationRequestView::TestApi view_test_api(view);
EXPECT_EQ(view_test_api.state(), LocalAuthenticationRequestViewState::kError);
}
// Tests close button successfully close the widget.
TEST_F(LocalAuthenticationRequestControllerImplTest,
LocalAuthenticationRequestClose) {
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
StartLocalAuthenticationRequest();
LocalAuthenticationRequestView* view =
LocalAuthenticationRequestWidget::GetViewForTesting();
ASSERT_NE(view, nullptr);
views::Button* close_button =
LocalAuthenticationRequestView::TestApi(view).close_button();
SimulateButtonPress(close_button);
EXPECT_EQ(0, successful_validation_);
EXPECT_EQ(1, close_action_);
EXPECT_FALSE(LocalAuthenticationRequestWidget::Get());
}
} // namespace
} // namespace ash

@ -0,0 +1,409 @@
// 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_view.h"
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/login/ui/arrow_button_view.h"
#include "ash/login/ui/local_authentication_request_widget.h"
#include "ash/login/ui/non_accessible_view.h"
#include "ash/public/cpp/accessibility_controller.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "ash/public/cpp/login/login_utils.h"
#include "ash/public/cpp/session/user_info.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/icon_button.h"
#include "ash/style/system_shadow.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "base/functional/bind.h"
#include "chromeos/ash/components/cryptohome/auth_factor.h"
#include "chromeos/ash/components/cryptohome/common_types.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "chromeos/ash/components/login/auth/public/session_auth_factors.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/account_id/account_id.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/resources/grit/ui_chromeos_resources.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/views/background.h"
#include "ui/views/controls/label.h"
#include "ui/views/highlight_border.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/vector_icons.h"
namespace ash {
namespace {
// TODO(b/304754895): move the pin request view shared constants to
// ash/login/ui/login_constants.h
constexpr int kLocalAuthenticationRequestViewVerticalInsetDp = 8;
// Minimum inset (= back button inset).
constexpr int kLocalAuthenticationRequestViewHorizontalInsetDp = 8;
constexpr int kLocalAuthenticationRequestViewRoundedCornerRadiusDp = 8;
constexpr int kLocalAuthenticationRequestViewWidthDp = 340;
constexpr int kLocalAuthenticationRequestViewHeightDp = 300;
constexpr int kIconToTitleDistanceDp = 24;
constexpr int kTitleToDescriptionDistanceDp = 8;
constexpr int kDescriptionToAccessCodeDistanceDp = 32;
constexpr int kSubmitButtonBottomMarginDp = 28;
constexpr int kTitleFontSizeDeltaDp = 4;
constexpr int kTitleLineWidthDp = 268;
constexpr int kTitleLineHeightDp = 24;
constexpr int kTitleMaxLines = 4;
constexpr int kDescriptionFontSizeDeltaDp = 0;
constexpr int kDescriptionLineWidthDp = 268;
constexpr int kDescriptionTextLineHeightDp = 18;
constexpr int kDescriptionMaxLines = 4;
constexpr int kAvatarSizeDp = 36;
constexpr int kCrossSizeDp = 20;
constexpr int kBackButtonSizeDp = 36;
constexpr int kLockIconSizeDp = 24;
constexpr int kBackButtonLockIconVerticalOverlapDp = 8;
constexpr int kHeaderHeightDp =
kBackButtonSizeDp + kLockIconSizeDp - kBackButtonLockIconVerticalOverlapDp;
} // namespace
LocalAuthenticationRequestView::TestApi::TestApi(
LocalAuthenticationRequestView* view)
: view_(view) {
CHECK(view_);
}
LocalAuthenticationRequestView::TestApi::~TestApi() {
view_ = nullptr;
}
LoginButton* LocalAuthenticationRequestView::TestApi::close_button() {
return view_->close_button_;
}
views::Label* LocalAuthenticationRequestView::TestApi::title_label() {
return view_->title_label_;
}
views::Label* LocalAuthenticationRequestView::TestApi::description_label() {
return view_->description_label_;
}
LoginPasswordView*
LocalAuthenticationRequestView::TestApi::login_password_view() {
return view_->login_password_view_;
}
views::Textfield* LocalAuthenticationRequestView::TestApi::GetInputTextfield()
const {
return LoginPasswordView::TestApi(view_->login_password_view_).textfield();
}
LocalAuthenticationRequestViewState
LocalAuthenticationRequestView::TestApi::state() const {
return view_->state_;
}
LocalAuthenticationRequestView::LocalAuthenticationRequestView(
OnLocalAuthenticationCompleted on_local_authentication_completed,
const std::u16string& title,
const std::u16string& description,
Delegate* delegate,
std::unique_ptr<UserContext> user_context)
: on_local_authentication_completed_(
std::move(on_local_authentication_completed)),
delegate_(delegate),
default_title_(title),
default_description_(description),
auth_performer_(UserDataAuthClient::Get()),
user_context_(std::move(user_context)) {
// MODAL_TYPE_SYSTEM 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.
SetModalType(ui::MODAL_TYPE_SYSTEM);
const bool is_jelly = chromeos::features::IsJellyEnabled();
// Main view contains all other views aligned vertically and centered.
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets::VH(kLocalAuthenticationRequestViewVerticalInsetDp,
kLocalAuthenticationRequestViewHorizontalInsetDp),
0);
layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
SetLayoutManager(std::move(layout));
// Set Backgground color and shape.
SetPaintToLayer();
layer()->SetBackgroundBlur(ShelfConfig::Get()->shelf_blur_radius());
ui::ColorId background_color_id =
is_jelly ? cros_tokens::kCrosSysSystemBaseElevated
: static_cast<ui::ColorId>(kColorAshShieldAndBase80);
SetBackground(views::CreateThemedRoundedRectBackground(
background_color_id,
kLocalAuthenticationRequestViewRoundedCornerRadiusDp));
// Set Border and shadow.
SetBorder(std::make_unique<views::HighlightBorder>(
kLocalAuthenticationRequestViewRoundedCornerRadiusDp,
views::HighlightBorder::Type::kHighlightBorder1));
shadow_ = SystemShadow::CreateShadowOnNinePatchLayerForView(
this, SystemShadow::Type::kElevation12);
shadow_->SetRoundedCornerRadius(
kLocalAuthenticationRequestViewRoundedCornerRadiusDp);
// Header view which contains the back button that is aligned top right and
// the lock icon which is in the bottom center.
auto header_layout = std::make_unique<views::FillLayout>();
auto* header = new NonAccessibleView();
header->SetLayoutManager(std::move(header_layout));
AddChildView(header);
auto* header_spacer = new NonAccessibleView();
header_spacer->SetPreferredSize(gfx::Size(0, kHeaderHeightDp));
header->AddChildView(header_spacer);
// Main view user avatar.
auto* icon_view = new NonAccessibleView();
icon_view->SetPreferredSize(gfx::Size(0, kHeaderHeightDp));
auto icon_layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0);
icon_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kEnd);
icon_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
icon_view->SetLayoutManager(std::move(icon_layout));
header->AddChildView(icon_view);
auto* avatar_view =
icon_view->AddChildView(std::make_unique<AnimatedRoundedImageView>(
gfx::Size(kAvatarSizeDp, kAvatarSizeDp),
kAvatarSizeDp / 2 /*corner_radius*/));
const UserAvatar avatar =
BuildAshUserAvatarForAccountId(user_context_->GetAccountId());
avatar_view->SetImage(avatar.image);
// Close button. Note that it should be the last view added to |header| in
// order to be clickable.
auto* close_button_view = new NonAccessibleView();
close_button_view->SetPreferredSize(
gfx::Size(kLocalAuthenticationRequestViewWidthDp -
2 * kLocalAuthenticationRequestViewHorizontalInsetDp,
kHeaderHeightDp));
auto close_button_layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0);
close_button_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kEnd);
close_button_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
close_button_view->SetLayoutManager(std::move(close_button_layout));
header->AddChildView(close_button_view);
close_button_ = new LoginButton(base::BindRepeating(
&LocalAuthenticationRequestView::OnClose, base::Unretained(this)));
close_button_->SetPreferredSize(
gfx::Size(kBackButtonSizeDp, kBackButtonSizeDp));
const ui::ColorId icon_color_id =
is_jelly ? static_cast<ui::ColorId>(cros_tokens::kCrosSysOnSurface)
: kColorAshIconColorPrimary;
close_button_->SetImageModel(
views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(views::kIcCloseIcon, icon_color_id,
kCrossSizeDp));
close_button_->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
close_button_->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
close_button_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_BACK_BUTTON_ACCESSIBLE_NAME));
close_button_->SetFocusBehavior(FocusBehavior::ALWAYS);
close_button_view->AddChildView(close_button_.get());
auto add_spacer = [&](int height) {
auto* spacer = new NonAccessibleView();
spacer->SetPreferredSize(gfx::Size(0, height));
AddChildView(spacer);
};
add_spacer(kIconToTitleDistanceDp);
auto decorate_label = [](views::Label* label) {
label->SetSubpixelRenderingEnabled(false);
label->SetAutoColorReadabilityEnabled(false);
const ui::ColorId text_color_id =
chromeos::features::IsJellyEnabled()
? static_cast<ui::ColorId>(cros_tokens::kCrosSysOnSurface)
: kColorAshTextColorPrimary;
label->SetEnabledColorId(text_color_id);
label->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
};
// Main view title.
title_label_ = new views::Label(default_title_, views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY);
title_label_->SetMultiLine(true);
title_label_->SetMaxLines(kTitleMaxLines);
title_label_->SizeToFit(kTitleLineWidthDp);
title_label_->SetLineHeight(kTitleLineHeightDp);
title_label_->SetFontList(gfx::FontList().Derive(
kTitleFontSizeDeltaDp, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM));
decorate_label(title_label_);
AddChildView(title_label_.get());
add_spacer(kTitleToDescriptionDistanceDp);
// Main view description.
description_label_ =
new views::Label(default_description_, views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY);
description_label_->SetMultiLine(true);
description_label_->SetMaxLines(kDescriptionMaxLines);
description_label_->SizeToFit(kDescriptionLineWidthDp);
description_label_->SetLineHeight(kDescriptionTextLineHeightDp);
description_label_->SetFontList(
gfx::FontList().Derive(kDescriptionFontSizeDeltaDp, gfx::Font::NORMAL,
gfx::Font::Weight::NORMAL));
decorate_label(description_label_);
AddChildView(description_label_.get());
add_spacer(kDescriptionToAccessCodeDistanceDp);
login_password_view_ = AddChildView(std::make_unique<LoginPasswordView>());
login_password_view_->SetPaintToLayer();
login_password_view_->layer()->SetFillsBoundsOpaquely(false);
login_password_view_->SetDisplayPasswordButtonVisible(true);
login_password_view_->SetEnabledOnEmptyPassword(false);
login_password_view_->SetFocusEnabledForTextfield(true);
login_password_view_->SetPlaceholderText(
l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PASSWORD_PLACEHOLDER));
login_password_view_->Init(
base::BindRepeating(&LocalAuthenticationRequestView::OnAuthSubmit,
base::Unretained(this),
/*authenticated_by_pin=*/false),
base::BindRepeating(&LocalAuthenticationRequestView::OnInputTextChanged,
base::Unretained(this)));
add_spacer(kSubmitButtonBottomMarginDp);
SetPreferredSize(GetLocalAuthenticationRequestViewSize());
}
LocalAuthenticationRequestView::~LocalAuthenticationRequestView() = default;
void LocalAuthenticationRequestView::RequestFocus() {
login_password_view_->RequestFocus();
}
void LocalAuthenticationRequestView::SetInputEnabled(bool input_enabled) {
login_password_view_->SetReadOnly(!input_enabled);
}
void LocalAuthenticationRequestView::ClearInput() {
login_password_view_->Reset();
}
void LocalAuthenticationRequestView::UpdateState(
LocalAuthenticationRequestViewState state,
const std::u16string& title,
const std::u16string& description) {
state_ = state;
ui::ColorId color_id = state == LocalAuthenticationRequestViewState::kNormal
? cros_tokens::kCrosSysSystemBaseElevated
: cros_tokens::kCrosSysError;
title_label_->SetText(title);
description_label_->SetText(description);
description_label_->SetEnabledColorId(color_id);
}
gfx::Size LocalAuthenticationRequestView::CalculatePreferredSize() const {
return GetLocalAuthenticationRequestViewSize();
}
views::View* LocalAuthenticationRequestView::GetInitiallyFocusedView() {
return login_password_view_;
}
std::u16string LocalAuthenticationRequestView::GetAccessibleWindowTitle()
const {
return default_title_;
}
void LocalAuthenticationRequestView::OnClose() {
delegate_->OnClose();
if (LocalAuthenticationRequestWidget::Get()) {
LocalAuthenticationRequestWidget::Get()->Close(false /* success */);
}
}
void LocalAuthenticationRequestView::UpdatePreferredSize() {
SetPreferredSize(CalculatePreferredSize());
if (GetWidget()) {
GetWidget()->CenterWindow(GetPreferredSize());
}
}
gfx::Size
LocalAuthenticationRequestView::GetLocalAuthenticationRequestViewSize() const {
return gfx::Size(kLocalAuthenticationRequestViewWidthDp,
kLocalAuthenticationRequestViewHeightDp);
}
void LocalAuthenticationRequestView::OnAuthSubmit(
bool authenticated_by_pin,
const std::u16string& password) {
CHECK(!authenticated_by_pin);
SetInputEnabled(false);
const auto& auth_factors = user_context_->GetAuthFactorsData();
const cryptohome::AuthFactor* local_password_factor =
auth_factors.FindLocalPasswordFactor();
CHECK_NE(local_password_factor, nullptr);
const cryptohome::KeyLabel& key_label = local_password_factor->ref().label();
// Create a copy of `user_context_` so that we don't lose it to std::move
// for future auth attempts
auth_performer_.AuthenticateWithPassword(
key_label.value(), base::UTF16ToUTF8(password),
std::make_unique<UserContext>(*user_context_),
base::BindOnce(&LocalAuthenticationRequestView::OnAuthComplete,
weak_ptr_factory_.GetWeakPtr()));
}
void LocalAuthenticationRequestView::OnAuthComplete(
std::unique_ptr<UserContext> user_context,
absl::optional<AuthenticationError> authentication_error) {
if (authentication_error.has_value()) {
LOG(ERROR) << "An error happened during the attempt to validate "
"the password: "
<< authentication_error.value().get_cryptohome_code();
UpdateState(
LocalAuthenticationRequestViewState::kError, default_title_,
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_ERROR_AUTHENTICATING_PWD));
ClearInput();
SetInputEnabled(true);
} else {
LocalAuthenticationRequestWidget::Get()->Close(true /* success */);
}
}
void LocalAuthenticationRequestView::OnInputTextChanged(bool is_empty) {}
} // namespace ash

@ -0,0 +1,160 @@
// 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.
#ifndef ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_VIEW_H_
#define ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_VIEW_H_
#include <memory>
#include <string>
#include "ash/ash_export.h"
#include "ash/login/ui/access_code_input.h"
#include "ash/login/ui/login_button.h"
#include "ash/login/ui/login_password_view.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "ash/public/cpp/login_types.h"
#include "ash/public/cpp/tablet_mode_observer.h"
#include "ash/style/system_shadow.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "chromeos/ash/components/login/auth/auth_performer.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/controls/label.h"
#include "ui/views/window/dialog_delegate.h"
namespace views {
class Label;
} // namespace views
namespace ash {
class UserContext;
// State of the LocalAuthenticationView.
enum class LocalAuthenticationRequestViewState {
kNormal,
kError,
};
// The view that allows for input of local authentication to authorize certain
// actions.
class ASH_EXPORT LocalAuthenticationRequestView
: public views::DialogDelegateView {
public:
using OnLocalAuthenticationRequestDone =
base::OnceCallback<void(bool success)>;
class Delegate {
public:
virtual void OnClose() = 0;
protected:
virtual ~Delegate() = default;
};
class ASH_EXPORT TestApi {
public:
explicit TestApi(LocalAuthenticationRequestView* view);
~TestApi();
LoginButton* close_button();
views::Label* title_label();
views::Label* description_label();
LoginPasswordView* login_password_view();
views::Textfield* GetInputTextfield() const;
LocalAuthenticationRequestViewState state() const;
private:
raw_ptr<LocalAuthenticationRequestView, ExperimentalAsh> view_;
};
// Creates local authentication request view that will enable the user to
// authenticate with a local authentication.
LocalAuthenticationRequestView(
OnLocalAuthenticationCompleted on_local_authentication_completed,
const std::u16string& title,
const std::u16string& description,
Delegate* delegate,
std::unique_ptr<UserContext> user_context);
LocalAuthenticationRequestView(const LocalAuthenticationRequestView&) =
delete;
LocalAuthenticationRequestView& operator=(
const LocalAuthenticationRequestView&) = delete;
~LocalAuthenticationRequestView() override;
// views::DialogDelegateView:
void RequestFocus() override;
gfx::Size CalculatePreferredSize() const override;
views::View* GetInitiallyFocusedView() override;
std::u16string GetAccessibleWindowTitle() const override;
// Sets whether the user can enter a local authentication. Other buttons
// (back, submit etc.) are unaffected.
void SetInputEnabled(bool input_enabled);
// Clears previously entered local authentication from the input field.
void ClearInput();
// Updates state of the view.
void UpdateState(LocalAuthenticationRequestViewState state,
const std::u16string& title,
const std::u16string& description);
private:
void OnAuthSubmit(bool authenticated_by_pin, const std::u16string& password);
void OnAuthComplete(std::unique_ptr<UserContext> user_context,
absl::optional<AuthenticationError> authentication_error);
void OnInputTextChanged(bool is_empty);
void OnVisibilityChanged();
// Closes the view.
void OnClose();
// Updates view's preferred size.
void UpdatePreferredSize();
// Callback to close the UI.
OnLocalAuthenticationCompleted on_local_authentication_completed_ =
base::NullCallback();
// Returns the view dimensions.
gfx::Size GetLocalAuthenticationRequestViewSize() const;
// Unowned pointer to the delegate. The delegate should outlive this instance.
raw_ptr<Delegate, ExperimentalAsh> delegate_;
// Strings as on view construction to enable restoring the original state.
std::u16string default_title_;
std::u16string default_description_;
// Correspononding labels and other UI elements.
raw_ptr<views::Label, ExperimentalAsh> title_label_ = nullptr;
raw_ptr<views::Label, ExperimentalAsh> description_label_ = nullptr;
raw_ptr<LoginButton, ExperimentalAsh> close_button_ = nullptr;
raw_ptr<LoginPasswordView, ExperimentalAsh> login_password_view_ = nullptr;
std::unique_ptr<SystemShadow> shadow_;
// Current local authentication state.
LocalAuthenticationRequestViewState state_ =
LocalAuthenticationRequestViewState::kNormal;
AuthPerformer auth_performer_;
// Current user context.
std::unique_ptr<UserContext> user_context_;
base::WeakPtrFactory<LocalAuthenticationRequestView> weak_ptr_factory_{this};
};
} // namespace ash
#endif // ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_VIEW_H_

@ -0,0 +1,135 @@
// 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_widget.h"
#include <memory>
#include <string>
#include <utility>
#include "ash/keyboard/keyboard_controller_impl.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "components/session_manager/session_manager_types.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
LocalAuthenticationRequestWidget* g_instance = nullptr;
} // namespace
// static
void LocalAuthenticationRequestWidget::Show(
OnLocalAuthenticationCompleted on_local_authentication_completed,
const std::u16string& title,
const std::u16string& description,
LocalAuthenticationRequestView::Delegate* delegate,
std::unique_ptr<UserContext> user_context) {
CHECK(!g_instance);
g_instance = new LocalAuthenticationRequestWidget(
std::move(on_local_authentication_completed), title, description,
delegate, std::move(user_context));
}
// static
LocalAuthenticationRequestWidget* LocalAuthenticationRequestWidget::Get() {
return g_instance;
}
// static
void LocalAuthenticationRequestWidget::UpdateState(
LocalAuthenticationRequestViewState state,
const std::u16string& title,
const std::u16string& description) {
CHECK_EQ(g_instance, this);
GetView()->UpdateState(state, title, description);
}
void LocalAuthenticationRequestWidget::SetInputEnabled(bool enabled) {
CHECK_EQ(g_instance, this);
GetView()->SetInputEnabled(enabled);
}
void LocalAuthenticationRequestWidget::ClearInput() {
CHECK_EQ(g_instance, this);
GetView()->ClearInput();
}
void LocalAuthenticationRequestWidget::Close(bool success) {
CHECK_EQ(g_instance, this);
LocalAuthenticationRequestWidget* instance = g_instance;
g_instance = nullptr;
std::move(on_local_authentication_completed_).Run(success);
widget_->Close();
delete instance;
}
LocalAuthenticationRequestWidget::LocalAuthenticationRequestWidget(
OnLocalAuthenticationCompleted on_local_authentication_completed,
const std::u16string& title,
const std::u16string& description,
LocalAuthenticationRequestView::Delegate* delegate,
std::unique_ptr<UserContext> user_context)
: on_local_authentication_completed_(
std::move(on_local_authentication_completed)) {
views::Widget::InitParams widget_params;
// Using window frameless to be able to get focus on the view input fields,
// which does not work with popup type.
widget_params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
widget_params.ownership =
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget_params.opacity =
views::Widget::InitParams::WindowOpacity::kTranslucent;
widget_params.accept_events = true;
ShellWindowId parent_window_id =
Shell::Get()->session_controller()->GetSessionState() ==
session_manager::SessionState::ACTIVE
? kShellWindowId_SystemModalContainer
: kShellWindowId_LockSystemModalContainer;
widget_params.parent =
Shell::GetPrimaryRootWindow()->GetChildById(parent_window_id);
widget_params.delegate = new LocalAuthenticationRequestView(
base::BindOnce(&LocalAuthenticationRequestWidget::Close,
weak_factory_.GetWeakPtr()),
title, description, delegate, std::move(user_context));
widget_ = std::make_unique<views::Widget>();
widget_->Init(std::move(widget_params));
Show();
}
LocalAuthenticationRequestWidget::~LocalAuthenticationRequestWidget() = default;
void LocalAuthenticationRequestWidget::Show() {
CHECK(widget_);
widget_->Show();
}
// static
LocalAuthenticationRequestView*
LocalAuthenticationRequestWidget::GetViewForTesting() {
LocalAuthenticationRequestWidget* widget =
LocalAuthenticationRequestWidget::Get();
return widget ? widget->GetView() : nullptr;
}
LocalAuthenticationRequestView* LocalAuthenticationRequestWidget::GetView() {
return static_cast<LocalAuthenticationRequestView*>(
widget_->widget_delegate());
}
} // namespace ash

@ -0,0 +1,104 @@
// 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.
#ifndef ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_WIDGET_H_
#define ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_WIDGET_H_
#include <memory>
#include <string>
#include "ash/ash_export.h"
#include "ash/login/ui/local_authentication_request_view.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
namespace views {
class Widget;
}
namespace ash {
enum class LocalAuthenticationRequestReason;
enum class LocalAuthenticationRequestViewState;
class UserContext;
// Widget to display the Local Password Request View in a standalone container.
class ASH_EXPORT LocalAuthenticationRequestWidget {
public:
class ASH_EXPORT TestApi {
public:
TestApi();
~TestApi();
static LocalAuthenticationRequestView* GetView();
};
LocalAuthenticationRequestWidget(const LocalAuthenticationRequestWidget&) =
delete;
LocalAuthenticationRequestWidget& operator=(
const LocalAuthenticationRequestWidget&) = delete;
// Creates and shows the instance of LocalAuthenticationRequestWidget.
// This widget is modal and only one instance can be created at a time. It
// will be destroyed when dismissed.
static void Show(
OnLocalAuthenticationCompleted on_local_authentication_completed,
const std::u16string& title,
const std::u16string& description,
LocalAuthenticationRequestView::Delegate* delegate,
std::unique_ptr<UserContext> user_context);
// Returns the instance of LocalAuthenticationRequestWidget or nullptr if it
// does not exits.
static LocalAuthenticationRequestWidget* Get();
// Toggles showing an error state and updates displayed strings.
void UpdateState(LocalAuthenticationRequestViewState state,
const std::u16string& title,
const std::u16string& description);
// Enables or disables input textfield.
void SetInputEnabled(bool enabled);
// Clears previously entered password.
void ClearInput();
// Closes the widget.
// |success| describes whether the validation was successful and is passed to
// |on_local_authentication_request_done_|.
void Close(bool success);
// Returns the associated view for testing purposes.
static LocalAuthenticationRequestView* GetViewForTesting();
private:
LocalAuthenticationRequestWidget(
LocalAuthenticationRequestView::OnLocalAuthenticationRequestDone
on_local_authentication_request_done,
const std::u16string& title,
const std::u16string& description,
LocalAuthenticationRequestView::Delegate* delegate,
std::unique_ptr<UserContext> user_context);
~LocalAuthenticationRequestWidget();
// Shows the |widget_|.
void Show();
// Returns the associated view.
LocalAuthenticationRequestView* GetView();
// Callback invoked when closing the widget.
LocalAuthenticationRequestView::OnLocalAuthenticationRequestDone
on_local_authentication_completed_;
std::unique_ptr<views::Widget> widget_;
base::WeakPtrFactory<LocalAuthenticationRequestWidget> weak_factory_{this};
};
} // namespace ash
#endif // ASH_LOGIN_UI_LOCAL_AUTHENTICATION_REQUEST_WIDGET_H_

@ -15,6 +15,7 @@
#include "ash/detachable_base/detachable_base_pairing_status.h"
#include "ash/ime/ime_controller_impl.h"
#include "ash/login/login_screen_controller.h"
#include "ash/login/ui/local_authentication_request_controller_impl.h"
#include "ash/login/ui/lock_contents_view.h"
#include "ash/login/ui/lock_screen.h"
#include "ash/login/ui/login_data_dispatcher.h"
@ -22,6 +23,7 @@
#include "ash/login/ui/non_accessible_view.h"
#include "ash/login/ui/views_utils.h"
#include "ash/public/cpp/kiosk_app_menu.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "ash/public/cpp/login_types.h"
#include "ash/public/cpp/smartlock_state.h"
#include "ash/public/cpp/style/dark_light_mode_controller.h"
@ -36,11 +38,13 @@
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/multi_user/multi_user_sign_in_policy.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/base/ime/ash/ime_keyboard.h"
#include "ui/chromeos/resources/grit/ui_chromeos_resources.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/scroll_view.h"
@ -381,6 +385,20 @@ class LockDebugView::DebugDataDispatcherTransformer
debug_user->account_id, debug_user->enable_tap_to_unlock);
}
// Activates authentication request dialog for the user at |user_index|.
void AuthRequestForUserIndex(size_t user_index) {
DCHECK(user_index >= 0 && user_index < debug_users_.size());
UserMetadata* debug_user = &debug_users_[user_index];
const AccountId account_id = debug_user->account_id;
std::unique_ptr<ash::UserContext> user_context =
std::make_unique<ash::UserContext>(user_manager::USER_TYPE_REGULAR,
account_id);
Shell::Get()->local_authentication_request_controller()->ShowWidget(
base::BindOnce([](bool bla) {}), std::move(user_context));
}
// Cycles fingerprint state for the user at |user_index|.
void CycleFingerprintStateForUserIndex(size_t user_index) {
DCHECK(user_index >= 0 && user_index < debug_users_.size());
@ -1229,6 +1247,12 @@ void LockDebugView::UpdatePerUserActionContainer() {
base::Unretained(debug_data_dispatcher_.get()), i),
row);
AddButton("Show local authentication request",
base::BindRepeating(
&DebugDataDispatcherTransformer::AuthRequestForUserIndex,
base::Unretained(debug_data_dispatcher_.get()), i),
row);
if (debug_detachable_base_model_->debugging_pairing_state() &&
debug_detachable_base_model_->GetPairingStatus() ==
DetachableBasePairingStatus::kAuthenticated) {

@ -200,6 +200,8 @@ component("cpp") {
"locale_update_controller.h",
"lock_screen_widget_factory.cc",
"lock_screen_widget_factory.h",
"login/local_authentication_request_controller.cc",
"login/local_authentication_request_controller.h",
"login/login_utils.cc",
"login/login_utils.h",
"login_accelerators.cc",

@ -0,0 +1,29 @@
// 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/public/cpp/login/local_authentication_request_controller.h"
#include "base/check_op.h"
namespace ash {
namespace {
LocalAuthenticationRequestController* g_instance = nullptr;
}
LocalAuthenticationRequestController::LocalAuthenticationRequestController() {
CHECK_EQ(nullptr, g_instance);
g_instance = this;
}
LocalAuthenticationRequestController::~LocalAuthenticationRequestController() {
CHECK_EQ(this, g_instance);
g_instance = nullptr;
}
LocalAuthenticationRequestController*
LocalAuthenticationRequestController::Get() {
return g_instance;
}
} // namespace ash

@ -0,0 +1,40 @@
// 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.
#ifndef ASH_PUBLIC_CPP_LOGIN_LOCAL_AUTHENTICATION_REQUEST_CONTROLLER_H_
#define ASH_PUBLIC_CPP_LOGIN_LOCAL_AUTHENTICATION_REQUEST_CONTROLLER_H_
#include <memory>
#include "ash/public/cpp/ash_public_export.h"
#include "base/functional/callback_forward.h"
namespace ash {
class UserContext;
using OnLocalAuthenticationCompleted = base::OnceCallback<void(bool success)>;
// LocalAuthenticationRequestController serves local authentication requests
// regarding the re-auth session. It takes care of showing and hiding the UI.
class ASH_PUBLIC_EXPORT LocalAuthenticationRequestController {
public:
static LocalAuthenticationRequestController* Get();
// Shows a standalone local authentication dialog.
// |callback| is invoked when the widget is closed e.g with the back button
// or the correct code is entered.
// Returns whether opening the dialog was successful. Will fail if another
// dialog is already opened.
virtual bool ShowWidget(OnLocalAuthenticationCompleted callback,
std::unique_ptr<UserContext> user_context) = 0;
protected:
LocalAuthenticationRequestController();
virtual ~LocalAuthenticationRequestController();
};
} // namespace ash
#endif // ASH_PUBLIC_CPP_LOGIN_LOCAL_AUTHENTICATION_REQUEST_CONTROLLER_H_

@ -86,6 +86,7 @@
#include "ash/keyboard/keyboard_controller_impl.h"
#include "ash/keyboard/ui/keyboard_ui_factory.h"
#include "ash/login/login_screen_controller.h"
#include "ash/login/ui/local_authentication_request_controller_impl.h"
#include "ash/login_status.h"
#include "ash/media/media_controller_impl.h"
#include "ash/metrics/feature_discovery_duration_reporter_impl.h"
@ -98,6 +99,7 @@
#include "ash/public/cpp/accelerator_keycode_lookup_cache.h"
#include "ash/public/cpp/ash_prefs.h"
#include "ash/public/cpp/holding_space/holding_space_controller.h"
#include "ash/public/cpp/login/local_authentication_request_controller.h"
#include "ash/public/cpp/nearby_share_delegate.h"
#include "ash/public/cpp/saved_desk_delegate.h"
#include "ash/public/cpp/shelf_config.h"
@ -664,6 +666,8 @@ Shell::Shell(std::unique_ptr<ShellDelegate> shell_delegate)
std::make_unique<KeyboardBrightnessController>()),
locale_update_controller_(std::make_unique<LocaleUpdateControllerImpl>()),
parent_access_controller_(std::make_unique<ParentAccessControllerImpl>()),
local_authentication_request_controller_(
std::make_unique<LocalAuthenticationRequestControllerImpl>()),
session_controller_(std::make_unique<SessionControllerImpl>()),
feature_discover_reporter_(
std::make_unique<FeatureDiscoveryDurationReporterImpl>(

@ -197,6 +197,7 @@ class NightLightControllerImpl;
class OcclusionTrackerPauser;
class OverviewController;
class ParentAccessController;
class LocalAuthenticationRequestControllerImpl;
class PartialMagnifierController;
class PciePeripheralNotificationController;
class UsbPeripheralNotificationController;
@ -603,6 +604,10 @@ class ASH_EXPORT Shell : public SessionObserver,
LocaleUpdateControllerImpl* locale_update_controller() {
return locale_update_controller_.get();
}
LocalAuthenticationRequestControllerImpl*
local_authentication_request_controller() {
return local_authentication_request_controller_.get();
}
LoginScreenController* login_screen_controller() {
return login_screen_controller_.get();
}
@ -1029,6 +1034,8 @@ class ASH_EXPORT Shell : public SessionObserver,
std::unique_ptr<NearbyShareControllerImpl> nearby_share_controller_;
std::unique_ptr<NearbyShareDelegate> nearby_share_delegate_;
std::unique_ptr<ParentAccessController> parent_access_controller_;
std::unique_ptr<LocalAuthenticationRequestControllerImpl>
local_authentication_request_controller_;
std::unique_ptr<PciePeripheralNotificationController>
pcie_peripheral_notification_controller_;
std::unique_ptr<PrivacyHubController> privacy_hub_controller_;