blob: 5b66b083222db42c488ffb94678b2b09c49b0b39 [file] [log] [blame]
// Copyright 2020 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 <stdint.h>
#include <memory>
#include <utility>
#include <vector>
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/login_screen_test_api.h"
#include "ash/public/cpp/session/session_controller.h"
#include "ash/public/cpp/session/session_observer.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/ash/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/ash/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/ash/certificate_provider/test_certificate_provider_extension.h"
#include "chrome/browser/ash/login/existing_user_controller.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/test/test_predicate_waiter.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/policy/extension_force_install_mixin.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chromeos/dbus/cryptohome/key.pb.h"
#include "chromeos/dbus/cryptohome/rpc.pb.h"
#include "chromeos/dbus/dbus_method_call_status.h"
#include "chromeos/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/login/auth/auth_status_consumer.h"
#include "chromeos/login/auth/challenge_response/known_user_pref_utils.h"
#include "components/account_id/account_id.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/session_manager/session_manager_types.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_manager.h"
#include "content/public/test/browser_test.h"
#include "extensions/common/features/simple_feature.h"
#include "net/base/net_errors.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/any_widget_observer.h"
using ash::LoginScreenTestApi;
namespace chromeos {
namespace {
// The PIN code that the test certificate provider extension is configured to
// expect.
constexpr char kCorrectPin[] = "17093";
constexpr char kWrongPin[] = "1234";
// UI golden strings in the en-US locale:
constexpr char16_t kChallengeResponseLoginLabel[] = u"Sign in with smart card";
constexpr char16_t kChallengeResponseErrorLabel[] =
u"Couldn’t recognize your smart card. Try again.";
constexpr char16_t kPinDialogDefaultTitle[] = u"Smart card PIN";
constexpr char16_t kPinDialogInvalidPinTitle[] = u"Invalid PIN.";
constexpr char16_t kPinDialogInvalidPin2AttemptsTitle[] =
u"Invalid PIN. 2 attempts left";
constexpr char16_t kPinDialogInvalidPin1AttemptTitle[] =
u"Invalid PIN. 1 attempt left";
constexpr char16_t kPinDialogNoAttemptsLeftTitle[] =
u"Maximum allowed attempts exceeded.";
constexpr char kChallengeData[] = "challenge";
// Returns the profile into which login-screen extensions are force-installed.
Profile* GetOriginalSigninProfile() {
return chromeos::ProfileHelper::GetSigninProfile()->GetOriginalProfile();
}
// Custom implementation of the UserDataAuthClient that triggers the
// challenge-response protocol when authenticating the user.
class ChallengeResponseFakeUserDataAuthClient : public FakeUserDataAuthClient {
public:
ChallengeResponseFakeUserDataAuthClient() = default;
ChallengeResponseFakeUserDataAuthClient(
const ChallengeResponseFakeUserDataAuthClient&) = delete;
ChallengeResponseFakeUserDataAuthClient& operator=(
const ChallengeResponseFakeUserDataAuthClient&) = delete;
~ChallengeResponseFakeUserDataAuthClient() override = default;
void set_challenge_response_account_id(const AccountId& account_id) {
challenge_response_account_id_ = account_id;
}
void Mount(
const ::user_data_auth::MountRequest& request,
DBusMethodCallback<::user_data_auth::MountReply> callback) override {
CertificateProviderService* certificate_provider_service =
CertificateProviderServiceFactory::GetForBrowserContext(
GetOriginalSigninProfile());
// Note: The real cryptohome would call the "ChallengeKey" D-Bus method
// exposed by Chrome via org.chromium.CryptohomeKeyDelegateInterface, but
// we're directly requesting the extension in order to avoid extra
// complexity in this UI-oriented browser test.
certificate_provider_service->RequestSignatureBySpki(
TestCertificateProviderExtension::GetCertificateSpki(),
SSL_SIGN_RSA_PKCS1_SHA256,
base::as_bytes(base::make_span(kChallengeData)),
challenge_response_account_id_,
base::BindOnce(&ChallengeResponseFakeUserDataAuthClient::
ContinueMountExWithSignature,
base::Unretained(this), request.account(),
std::move(callback)));
}
private:
void ContinueMountExWithSignature(
const cryptohome::AccountIdentifier& cryptohome_id,
DBusMethodCallback<::user_data_auth::MountReply> callback,
net::Error error,
const std::vector<uint8_t>& signature) {
::user_data_auth::MountReply reply;
reply.set_sanitized_username(GetStubSanitizedUsername(cryptohome_id));
if (error != net::OK || signature.empty())
reply.set_error(
::user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_MOUNT_FATAL);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), reply));
}
AccountId challenge_response_account_id_;
};
// Helper that allows to wait until the authentication failure is reported.
class AuthFailureWaiter final : public AuthStatusConsumer {
public:
AuthFailureWaiter() {
ExistingUserController::current_controller()->AddLoginStatusConsumer(this);
}
AuthFailureWaiter(const AuthFailureWaiter&) = delete;
AuthFailureWaiter& operator=(const AuthFailureWaiter&) = delete;
~AuthFailureWaiter() override {
ExistingUserController::current_controller()->RemoveLoginStatusConsumer(
this);
}
AuthFailure::FailureReason Wait() {
run_loop_.Run();
return failure_reason_;
}
// AuthStatusConsumer:
void OnAuthFailure(const AuthFailure& error) override {
failure_reason_ = error.reason();
run_loop_.Quit();
}
void OnAuthSuccess(const UserContext& user_context) override {}
private:
base::RunLoop run_loop_;
AuthFailure::FailureReason failure_reason_ = AuthFailure::NONE;
};
// A helper class that blocks execution until Chrome is locking or terminating.
class ChromeSessionObserver : public ash::SessionObserver {
public:
ChromeSessionObserver() { ash::SessionController::Get()->AddObserver(this); }
ChromeSessionObserver(const ChromeSessionObserver&) = delete;
ChromeSessionObserver& operator=(const ChromeSessionObserver&) = delete;
~ChromeSessionObserver() override {
ash::SessionController::Get()->RemoveObserver(this);
}
void WaitForSessionLocked() { session_locked_loop_.Run(); }
void WaitForChromeTerminating() { termination_loop_.Run(); }
// ash::SessionObserver
void OnChromeTerminating() override { termination_loop_.Quit(); }
void OnSessionStateChanged(session_manager::SessionState state) override {
if (state == session_manager::SessionState::LOCKED)
session_locked_loop_.Quit();
}
private:
base::RunLoop session_locked_loop_;
base::RunLoop termination_loop_;
};
} // namespace
// Tests the challenge-response based login (e.g., using a smart card) for an
// existing user.
class SecurityTokenLoginTest : public MixinBasedInProcessBrowserTest,
public LocalStateMixin::Delegate {
protected:
SecurityTokenLoginTest()
: cryptohome_client_(new ChallengeResponseFakeUserDataAuthClient) {
// Don't shut down when no browser is open, since it breaks the test and
// since it's not the real Chrome OS behavior.
set_exit_when_last_browser_closes(false);
login_manager_mixin_.AppendManagedUsers(1);
cryptohome_client_->set_challenge_response_account_id(
GetChallengeResponseAccountId());
}
SecurityTokenLoginTest(const SecurityTokenLoginTest&) = delete;
SecurityTokenLoginTest& operator=(const SecurityTokenLoginTest&) = delete;
~SecurityTokenLoginTest() override = default;
// MixinBasedInProcessBrowserTest:
void SetUpCommandLine(base::CommandLine* command_line) override {
MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(chromeos::switches::kLoginManager);
command_line->AppendSwitch(chromeos::switches::kForceLoginManagerInTests);
// Avoid aborting the user sign-in due to the user policy requests not being
// faked in the test.
command_line->AppendSwitch(
chromeos::switches::kAllowFailedPolicyFetchForTest);
}
void SetUpInProcessBrowserTestFixture() override {
MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
// Init the user policy provider.
policy_provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
policy_provider_.SetAutoRefresh();
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&policy_provider_);
}
void SetUpOnMainThread() override {
MixinBasedInProcessBrowserTest::SetUpOnMainThread();
WaitForLoginScreenWidgetShown();
}
void TearDownOnMainThread() override {
certificate_provider_extension_.reset();
MixinBasedInProcessBrowserTest::TearDownOnMainThread();
}
// LocalStateMixin::Delegate:
void SetUpLocalState() override { RegisterChallengeResponseKey(); }
AccountId GetChallengeResponseAccountId() const {
return login_manager_mixin_.users()[0].account_id;
}
TestCertificateProviderExtension* certificate_provider_extension() {
return certificate_provider_extension_.get();
}
void StartLoginAndWaitForPinDialog() {
base::RunLoop pin_dialog_waiting_run_loop;
LoginScreenTestApi::SetPinRequestWidgetShownCallback(
pin_dialog_waiting_run_loop.QuitClosure());
LoginScreenTestApi::ClickChallengeResponseButton(
GetChallengeResponseAccountId());
pin_dialog_waiting_run_loop.Run();
}
void WaitForChallengeResponseLabel(const std::u16string& awaited_label) {
test::TestPredicateWaiter waiter(base::BindRepeating(
[](const AccountId& account_id, const std::u16string& awaited_label) {
return LoginScreenTestApi::GetChallengeResponseLabel(account_id) ==
awaited_label;
},
GetChallengeResponseAccountId(), awaited_label));
waiter.Wait();
}
void WaitForPinDialogTitle(const std::u16string& awaited_title) {
test::TestPredicateWaiter waiter(base::BindRepeating(
[](const std::u16string& awaited_title) {
return LoginScreenTestApi::GetPinRequestWidgetTitle() ==
awaited_title;
},
awaited_title));
waiter.Wait();
}
void WaitForActiveSession() { login_manager_mixin_.WaitForActiveSession(); }
// Configures and installs the login screen certificate provider extension.
void PrepareCertificateProviderExtension() {
certificate_provider_extension_ =
std::make_unique<TestCertificateProviderExtension>(
GetOriginalSigninProfile());
certificate_provider_extension_->set_require_pin(kCorrectPin);
extension_force_install_mixin_.InitWithMockPolicyProvider(
GetOriginalSigninProfile(), policy_provider());
EXPECT_TRUE(extension_force_install_mixin_.ForceInstallFromSourceDir(
TestCertificateProviderExtension::GetExtensionSourcePath(),
TestCertificateProviderExtension::GetExtensionPemPath(),
ExtensionForceInstallMixin::WaitMode::kBackgroundPageFirstLoad));
certificate_provider_extension_->TriggerSetCertificates();
}
// Waits until the Login or Lock screen is shown.
void WaitForLoginScreenWidgetShown() {
base::RunLoop run_loop;
LoginScreenTestApi::AddOnLockScreenShownCallback(run_loop.QuitClosure());
run_loop.Run();
}
LoginManagerMixin* login_manager_mixin() { return &login_manager_mixin_; }
policy::MockConfigurationPolicyProvider* policy_provider() {
return &policy_provider_;
}
private:
void RegisterChallengeResponseKey() {
// The global user manager is not created until after the Local State is
// initialized, but in order for the user_manager::known_user:: methods to
// work we create a temporary instance of the user manager here.
auto user_manager = std::make_unique<user_manager::FakeUserManager>();
user_manager->set_local_state(g_browser_process->local_state());
user_manager::ScopedUserManager scoper(std::move(user_manager));
ChallengeResponseKey challenge_response_key;
challenge_response_key.set_public_key_spki_der(
TestCertificateProviderExtension::GetCertificateSpki());
challenge_response_key.set_extension_id(
TestCertificateProviderExtension::extension_id());
base::Value challenge_response_keys_value =
SerializeChallengeResponseKeysForKnownUser({challenge_response_key});
user_manager::known_user::SetChallengeResponseKeys(
GetChallengeResponseAccountId(),
std::move(challenge_response_keys_value));
}
// Bypass "signin_screen" feature only enabled for allowlisted extensions.
extensions::SimpleFeature::ScopedThreadUnsafeAllowlistForTest
feature_allowlist_{TestCertificateProviderExtension::extension_id()};
// Unowned (referencing a global singleton)
ChallengeResponseFakeUserDataAuthClient* const cryptohome_client_;
LoginManagerMixin login_manager_mixin_{&mixin_host_};
LocalStateMixin local_state_mixin_{&mixin_host_, this};
ExtensionForceInstallMixin extension_force_install_mixin_{&mixin_host_};
testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
std::unique_ptr<TestCertificateProviderExtension>
certificate_provider_extension_;
};
// Tests the successful challenge-response login flow, including entering the
// correct PIN.
IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, Basic) {
PrepareCertificateProviderExtension();
// The user pod is displayed with the challenge-response "start" button
// instead of the password input field.
EXPECT_TRUE(LoginScreenTestApi::FocusUser(GetChallengeResponseAccountId()));
EXPECT_FALSE(LoginScreenTestApi::IsPasswordFieldShown(
GetChallengeResponseAccountId()));
EXPECT_EQ(LoginScreenTestApi::GetChallengeResponseLabel(
GetChallengeResponseAccountId()),
kChallengeResponseLoginLabel);
// The challenge-response "start" button is clicked. The MountEx request is
// sent to cryptohome, and in turn cryptohome makes a challenge request. The
// certificate provider extension receives this request and requests the PIN
// dialog.
StartLoginAndWaitForPinDialog();
EXPECT_EQ(LoginScreenTestApi::GetPinRequestWidgetTitle(),
kPinDialogDefaultTitle);
// The PIN is entered.
LoginScreenTestApi::SubmitPinRequestWidget(kCorrectPin);
// The PIN is received by the certificate provider extension, which replies to
// the challenge request. cryptohome receives this response and completes the
// MountEx request. The user session begins.
WaitForActiveSession();
}
// Test the login failure scenario when the certificate provider extension is
// missing.
IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, MissingExtension) {
EXPECT_EQ(LoginScreenTestApi::GetChallengeResponseLabel(
GetChallengeResponseAccountId()),
kChallengeResponseLoginLabel);
LoginScreenTestApi::ClickChallengeResponseButton(
GetChallengeResponseAccountId());
// An error will be shown after the login attempt gets rejected (note that the
// rejection happens before the actual authentication begins, which is why
// AuthFailureWaiter cannot be used in this test).
WaitForChallengeResponseLabel(kChallengeResponseErrorLabel);
}
// Test the login failure scenario when the PIN dialog gets canceled.
IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, PinCancel) {
PrepareCertificateProviderExtension();
StartLoginAndWaitForPinDialog();
// The PIN dialog is canceled. The login attempt is aborted.
AuthFailureWaiter auth_failure_waiter;
LoginScreenTestApi::CancelPinRequestWidget();
EXPECT_EQ(auth_failure_waiter.Wait(),
AuthFailure::COULD_NOT_MOUNT_CRYPTOHOME);
// No error is shown, and a new login attempt is allowed.
EXPECT_TRUE(LoginScreenTestApi::IsChallengeResponseButtonClickable(
GetChallengeResponseAccountId()));
EXPECT_EQ(LoginScreenTestApi::GetChallengeResponseLabel(
GetChallengeResponseAccountId()),
kChallengeResponseLoginLabel);
}
// Test the successful login scenario when the correct PIN was entered only on
// the second attempt.
IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, WrongPinThenCorrect) {
PrepareCertificateProviderExtension();
StartLoginAndWaitForPinDialog();
// A wrong PIN is entered, and an error is shown in the PIN dialog.
LoginScreenTestApi::SubmitPinRequestWidget(kWrongPin);
WaitForPinDialogTitle(kPinDialogInvalidPinTitle);
// The correct PIN is entered, and the login succeeds.
LoginScreenTestApi::SubmitPinRequestWidget(kCorrectPin);
WaitForActiveSession();
}
// Test the login failure scenario when the wrong PIN is entered several times
// until there's no more attempt left (simulating, e.g., a smart card lockout).
IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, WrongPinUntilLockout) {
PrepareCertificateProviderExtension();
certificate_provider_extension()->set_remaining_pin_attempts(3);
StartLoginAndWaitForPinDialog();
// A wrong PIN is entered several times, causing a corresponding error
// displayed in the PIN dialog.
LoginScreenTestApi::SubmitPinRequestWidget(kWrongPin);
WaitForPinDialogTitle(kPinDialogInvalidPin2AttemptsTitle);
LoginScreenTestApi::SubmitPinRequestWidget(kWrongPin);
WaitForPinDialogTitle(kPinDialogInvalidPin1AttemptTitle);
LoginScreenTestApi::SubmitPinRequestWidget(kWrongPin);
WaitForPinDialogTitle(kPinDialogNoAttemptsLeftTitle);
// After closing the PIN dialog with the fatal error, the login fails.
AuthFailureWaiter auth_failure_waiter;
LoginScreenTestApi::CancelPinRequestWidget();
EXPECT_EQ(auth_failure_waiter.Wait(),
AuthFailure::COULD_NOT_MOUNT_CRYPTOHOME);
}
// Test the login failure scenario when the extension fails to sign the
// challenge.
IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, SigningFailure) {
PrepareCertificateProviderExtension();
certificate_provider_extension()->set_should_fail_sign_digest_requests(true);
AuthFailureWaiter auth_failure_waiter;
LoginScreenTestApi::ClickChallengeResponseButton(
GetChallengeResponseAccountId());
EXPECT_EQ(auth_failure_waiter.Wait(),
AuthFailure::COULD_NOT_MOUNT_CRYPTOHOME);
// An error is shown.
EXPECT_TRUE(LoginScreenTestApi::IsChallengeResponseButtonClickable(
GetChallengeResponseAccountId()));
EXPECT_EQ(LoginScreenTestApi::GetChallengeResponseLabel(
GetChallengeResponseAccountId()),
kChallengeResponseErrorLabel);
}
// Tests for the SecurityTokenSessionBehavior and
// SecurityTokenSessionNotificationSeconds policies.
class SecurityTokenSessionBehaviorTest : public SecurityTokenLoginTest {
protected:
SecurityTokenSessionBehaviorTest() = default;
SecurityTokenSessionBehaviorTest(const SecurityTokenSessionBehaviorTest&) =
delete;
SecurityTokenSessionBehaviorTest& operator=(
const SecurityTokenSessionBehaviorTest&) = delete;
~SecurityTokenSessionBehaviorTest() override = default;
void Login() {
PrepareCertificateProviderExtension();
StartLoginAndWaitForPinDialog();
LoginScreenTestApi::SubmitPinRequestWidget(kCorrectPin);
WaitForActiveSession();
profile_ = chromeos::ProfileHelper::Get()->GetProfileByAccountId(
GetChallengeResponseAccountId());
}
// Configures and installs the user session certificate provider extension.
void PrepareUserCertificateProviderExtension() {
user_certificate_provider_extension_ =
std::make_unique<TestCertificateProviderExtension>(profile());
user_extension_mixin_.InitWithMockPolicyProvider(profile(),
policy_provider());
EXPECT_TRUE(user_extension_mixin_.ForceInstallFromSourceDir(
TestCertificateProviderExtension::GetExtensionSourcePath(),
TestCertificateProviderExtension::GetExtensionPemPath(),
ExtensionForceInstallMixin::WaitMode::kBackgroundPageFirstLoad));
}
// Makes the user session extension call certificateProvider.setCertificates()
// without providing any certificates, thus simulating the removal of a
// security token.
void SimulateSecurityTokenRemoval() {
ASSERT_TRUE(user_certificate_provider_extension_);
user_certificate_provider_extension()->set_should_provide_certificates(
false);
user_certificate_provider_extension()->TriggerSetCertificates();
}
bool ProfileHasNotification(Profile* profile,
const std::string& notification_id) {
NotificationDisplayService* notification_display_service =
NotificationDisplayService::GetForProfile(profile);
if (!notification_display_service) {
ADD_FAILURE() << "NotificationDisplayService could not be found.";
return false;
}
base::RunLoop run_loop;
bool has_notification = false;
notification_display_service->GetDisplayed(
base::BindLambdaForTesting([&](std::set<std::string> notification_ids,
bool /* supports_synchronization */) {
has_notification = notification_ids.count(notification_id) >= 1;
run_loop.Quit();
}));
run_loop.Run();
return has_notification;
}
Profile* profile() const { return profile_; }
TestCertificateProviderExtension* user_certificate_provider_extension()
const {
return user_certificate_provider_extension_.get();
}
private:
ExtensionForceInstallMixin user_extension_mixin_{&mixin_host_};
std::unique_ptr<TestCertificateProviderExtension>
user_certificate_provider_extension_;
Profile* profile_ = nullptr;
};
// Tests the SecurityTokenSessionBehavior policy with value "LOCK".
IN_PROC_BROWSER_TEST_F(SecurityTokenSessionBehaviorTest, Lock) {
Login();
profile()->GetPrefs()->SetString(prefs::kSecurityTokenSessionBehavior,
"LOCK");
PrepareUserCertificateProviderExtension();
ChromeSessionObserver chrome_session_observer;
SimulateSecurityTokenRemoval();
chrome_session_observer.WaitForSessionLocked();
EXPECT_TRUE(ProfileHasNotification(
profile(), "security_token_session_controller_notification"));
EXPECT_TRUE(profile()->GetPrefs()->GetBoolean(
prefs::kSecurityTokenSessionNotificationDisplayed));
}
// Tests the SecurityTokenSessionBehavior policy with value "LOGOUT".
IN_PROC_BROWSER_TEST_F(SecurityTokenSessionBehaviorTest, PRE_Logout) {
Login();
ChromeSessionObserver chrome_session_observer;
profile()->GetPrefs()->SetString(prefs::kSecurityTokenSessionBehavior,
"LOGOUT");
PrepareUserCertificateProviderExtension();
// Removal of the certificate should lead to the end of the current session.
SimulateSecurityTokenRemoval();
chrome_session_observer.WaitForChromeTerminating();
// Check login screen notification is scheduled.
EXPECT_TRUE(profile()->GetPrefs()->GetBoolean(
prefs::kSecurityTokenSessionNotificationDisplayed));
}
IN_PROC_BROWSER_TEST_F(SecurityTokenSessionBehaviorTest, Logout) {
// Check login screen notification is displayed.
EXPECT_TRUE(
ProfileHasNotification(GetOriginalSigninProfile(),
"security_token_session_controller_notification"));
}
// Tests the SecurityTokenSessionNotificationSeconds policy.
IN_PROC_BROWSER_TEST_F(SecurityTokenSessionBehaviorTest, NotificationSeconds) {
Login();
profile()->GetPrefs()->SetString(prefs::kSecurityTokenSessionBehavior,
"LOCK");
profile()->GetPrefs()->SetInteger(
prefs::kSecurityTokenSessionNotificationSeconds, 1);
PrepareUserCertificateProviderExtension();
ChromeSessionObserver chrome_session_observer;
views::NamedWidgetShownWaiter notification_waiter(
views::test::AnyWidgetTestPasskey{},
"SecurityTokenSessionRestrictionView");
SimulateSecurityTokenRemoval();
views::Widget* notification = notification_waiter.WaitIfNeededAndGet();
views::test::WidgetClosingObserver notification_closing_observer(
notification);
notification_closing_observer.Wait();
// After the notification expires, the device gets locked.
chrome_session_observer.WaitForSessionLocked();
// The notification no longer exists.
EXPECT_TRUE(notification_closing_observer.widget_closed());
}
} // namespace chromeos