Commit afc265a0 authored by Fabian Sommer's avatar Fabian Sommer Committed by Chromium LUCI CQ

Add policies for logout on smart card removal

This CL implements the core part of SecurityTokenSessionController.
The controller observes security tokens and triggers lock / logout when
a token that was used for login is removed, alongside UI implemented in
previous CLs.

This behavior can be controlled by the SecurityTokenSessionBehavior and
SecurityTokenSessionNotificationSeconds policies, which this CL
activates.

This CL also adds browser tests for both policies. In doing so, it also
adds a test for SecurityTokenRestrictionView.

The CL renames the should_fail_certificate_requests property in
TestCertificateProviderExtension to should_provide_certificates,
which more accurately reflects its purpose.

Fixed: 1131450
Change-Id: I8cf4e25fbbcb8ae4ab607ace254a85197f594f5a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2632990
Commit-Queue: Fabian Sommer <fabiansommer@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Cr-Commit-Position: refs/heads/master@{#846276}
parent 3160ebc7
......@@ -205,7 +205,7 @@ void TestCertificateProviderExtension::TriggerSetCertificates() {
base::Value message_data(base::Value::Type::DICTIONARY);
message_data.SetStringKey("name", "setCertificates");
base::Value cert_info_values(base::Value::Type::LIST);
if (!should_fail_certificate_requests_)
if (should_provide_certificates_)
cert_info_values.Append(MakeClientCertificateInfoValue(*certificate_));
message_data.SetKey("certificateInfoList", std::move(cert_info_values));
......@@ -266,7 +266,7 @@ void TestCertificateProviderExtension::HandleCertificatesRequest(
ReplyToJsCallback callback) {
++certificate_request_count_;
base::Value cert_info_values(base::Value::Type::LIST);
if (!should_fail_certificate_requests_)
if (should_provide_certificates_)
cert_info_values.Append(MakeClientCertificateInfoValue(*certificate_));
std::move(callback).Run(cert_info_values);
}
......
......@@ -73,11 +73,10 @@ class TestCertificateProviderExtension final
remaining_pin_attempts_ = remaining_pin_attempts;
}
// Sets whether the extension should respond with a failure to the
// onCertificatesRequested requests.
void set_should_fail_certificate_requests(
bool should_fail_certificate_requests) {
should_fail_certificate_requests_ = should_fail_certificate_requests;
// Sets whether the extension should return any certificates in response to a
// onCertificatesRequested request or a TriggerSetCertificates() call.
void set_should_provide_certificates(bool should_provide_certificates) {
should_provide_certificates_ = should_provide_certificates;
}
// Sets whether the extension should respond with a failure to the
......@@ -113,7 +112,7 @@ class TestCertificateProviderExtension final
// When equal to zero, signature requests will be failed immediately; when is
// negative, infinite number of attempts is allowed.
int remaining_pin_attempts_ = -1;
bool should_fail_certificate_requests_ = false;
bool should_provide_certificates_ = true;
bool should_fail_sign_digest_requests_ = false;
content::NotificationRegistrar notification_registrar_;
......
......@@ -250,7 +250,7 @@ IN_PROC_BROWSER_TEST_F(CryptohomeKeyDelegateServiceProviderTest,
// by the test provider anymore.
IN_PROC_BROWSER_TEST_F(CryptohomeKeyDelegateServiceProviderTest,
SignatureErrorKeyRemoved) {
certificate_provider_extension()->set_should_fail_certificate_requests(true);
certificate_provider_extension()->set_should_provide_certificates(false);
RefreshCertsFromCertProviders();
std::vector<uint8_t> signature;
......
......@@ -9,6 +9,8 @@
#include <vector>
#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"
......@@ -18,16 +20,18 @@
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/chromeos/certificate_provider/test_certificate_provider_extension.h"
#include "chrome/browser/chromeos/login/existing_user_controller.h"
#include "chrome/browser/chromeos/login/test/device_state_mixin.h"
#include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
#include "chrome/browser/chromeos/login/test/test_predicate_waiter.h"
#include "chrome/browser/chromeos/profiles/profile_helper.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/constants/chromeos_switches.h"
#include "chromeos/dbus/cryptohome/cryptohome_client.h"
......@@ -38,6 +42,9 @@
#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"
......@@ -47,6 +54,8 @@
#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;
......@@ -168,6 +177,35 @@ class AuthFailureWaiter final : public AuthStatusConsumer {
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
......@@ -204,6 +242,18 @@ class SecurityTokenLoginTest : public MixinBasedInProcessBrowserTest,
chromeos::switches::kAllowFailedPolicyFetchForTest);
}
void SetUpInProcessBrowserTestFixture() override {
MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
// Init the user policy provider.
EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(policy_provider_, IsFirstPolicyLoadComplete(testing::_))
.WillRepeatedly(testing::Return(true));
policy_provider_.SetAutoRefresh();
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&policy_provider_);
}
void SetUpOnMainThread() override {
MixinBasedInProcessBrowserTest::SetUpOnMainThread();
WaitForLoginScreenWidgetShown();
......@@ -257,14 +307,14 @@ class SecurityTokenLoginTest : public MixinBasedInProcessBrowserTest,
void WaitForActiveSession() { login_manager_mixin_.WaitForActiveSession(); }
// Configures and installs the test certificate provider extension.
// 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_.InitWithDeviceStateMixin(
GetOriginalSigninProfile(), &device_state_mixin_);
extension_force_install_mixin_.InitWithMockPolicyProvider(
GetOriginalSigninProfile(), policy_provider());
EXPECT_TRUE(extension_force_install_mixin_.ForceInstallFromSourceDir(
TestCertificateProviderExtension::GetExtensionSourcePath(),
TestCertificateProviderExtension::GetExtensionPemPath(),
......@@ -272,6 +322,19 @@ class SecurityTokenLoginTest : public MixinBasedInProcessBrowserTest,
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
......@@ -294,23 +357,16 @@ class SecurityTokenLoginTest : public MixinBasedInProcessBrowserTest,
std::move(challenge_response_keys_value));
}
void WaitForLoginScreenWidgetShown() {
base::RunLoop run_loop;
LoginScreenTestApi::AddOnLockScreenShownCallback(run_loop.QuitClosure());
run_loop.Run();
}
// Bypass "signin_screen" feature only enabled for allowlisted extensions.
extensions::SimpleFeature::ScopedThreadUnsafeAllowlistForTest
feature_allowlist_{TestCertificateProviderExtension::extension_id()};
// Unowned (referencing a global singleton)
ChallengeResponseFakeCryptohomeClient* const cryptohome_client_;
DeviceStateMixin device_state_mixin_{
&mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
LoginManagerMixin login_manager_mixin_{&mixin_host_};
LocalStateMixin local_state_mixin_{&mixin_host_, this};
ExtensionForceInstallMixin extension_force_install_mixin_{&mixin_host_};
policy::MockConfigurationPolicyProvider policy_provider_;
std::unique_ptr<TestCertificateProviderExtension>
certificate_provider_extension_;
......@@ -440,4 +496,150 @@ IN_PROC_BROWSER_TEST_F(SecurityTokenLoginTest, SigningFailure) {
base::UTF8ToUTF16(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
......@@ -5,25 +5,46 @@
#include "chrome/browser/chromeos/login/security_token_session_controller.h"
#include <string>
#include <vector>
#include "ash/public/cpp/notification_utils.h"
#include "base/bind.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/notreached.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_info.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/chromeos/login/lock/screen_locker.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/security_token_session_restriction_view.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/login/auth/challenge_response/known_user_pref_utils.h"
#include "chromeos/login/auth/challenge_response_key.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user.h"
#include "extensions/common/extension_id.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "net/cert/asn1_util.h"
#include "net/cert/x509_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"
#include "url/gurl.h"
#include "url/url_constants.h"
......@@ -104,38 +125,127 @@ void DisplayNotification(const base::string16& title,
SystemNotificationHelper::GetInstance()->Display(*notification);
}
// Loads the persistently stored information about the challenge-response keys
// that can be used for authenticating the user.
void LoadStoredChallengeResponseSpkiKeysForUser(
const AccountId& account_id,
base::flat_map<std::string, std::vector<std::string>>* extension_to_spkis,
base::flat_set<std::string>* extension_ids) {
// TODO(crbug.com/1164373) This approach does not work for ephemeral users.
// Instead, only get the certificate that was actually used on the last login.
const base::Value known_user_value =
user_manager::known_user::GetChallengeResponseKeys(account_id);
std::vector<DeserializedChallengeResponseKey>
deserialized_challenge_response_keys;
DeserializeChallengeResponseKeyFromKnownUser(
known_user_value, &deserialized_challenge_response_keys);
for (const DeserializedChallengeResponseKey& challenge_response_key :
deserialized_challenge_response_keys) {
if (challenge_response_key.extension_id.empty())
continue;
extension_ids->insert(challenge_response_key.extension_id);
if (!extension_to_spkis->contains(challenge_response_key.extension_id)) {
(*extension_to_spkis)[challenge_response_key.extension_id] = {};
}
if (!challenge_response_key.public_key_spki_der.empty()) {
(*extension_to_spkis)[challenge_response_key.extension_id].push_back(
challenge_response_key.public_key_spki_der);
}
}
}
std::string GetSubjectPublicKeyInfo(const net::X509Certificate& certificate) {
base::StringPiece spki_bytes;
if (!net::asn1::ExtractSPKIFromDERCert(
net::x509_util::CryptoBufferAsStringPiece(certificate.cert_buffer()),
&spki_bytes)) {
return {};
}
return spki_bytes.as_string();
}
} // namespace
SecurityTokenSessionController::SecurityTokenSessionController(
PrefService* local_state,
PrefService* profile_prefs,
const user_manager::User* user)
: local_state_(local_state), profile_prefs_(profile_prefs), user_(user) {
const user_manager::User* user,
CertificateProviderService* certificate_provider_service)
: local_state_(local_state),
profile_prefs_(profile_prefs),
user_(user),
certificate_provider_service_(certificate_provider_service) {
DCHECK(local_state_);
DCHECK(profile_prefs_);
DCHECK(user_);
DCHECK(certificate_provider_service_);
certificate_provider_ =
certificate_provider_service_->CreateCertificateProvider();
LoadStoredChallengeResponseSpkiKeysForUser(
user_->GetAccountId(), &extension_to_spkis_, &observed_extensions_);
UpdateNotificationPref();
UpdateBehaviorPref();
behavior_ = GetBehaviorFromPref();
pref_change_registrar_.Init(profile_prefs_);
base::RepeatingClosure behavior_pref_changed_callback =
base::BindRepeating(&SecurityTokenSessionController::UpdateBehaviorPref,
base::Unretained(this));
weak_ptr_factory_.GetWeakPtr());
base::RepeatingClosure notification_pref_changed_callback =
base::BindRepeating(
&SecurityTokenSessionController::UpdateNotificationPref,
base::Unretained(this));
weak_ptr_factory_.GetWeakPtr());
pref_change_registrar_.Add(prefs::kSecurityTokenSessionBehavior,
behavior_pref_changed_callback);
pref_change_registrar_.Add(prefs::kSecurityTokenSessionNotificationSeconds,
notification_pref_changed_callback);
certificate_provider_service_->AddObserver(this);
}
SecurityTokenSessionController::~SecurityTokenSessionController() = default;
SecurityTokenSessionController::~SecurityTokenSessionController() {
certificate_provider_service_->RemoveObserver(this);
}
void SecurityTokenSessionController::Shutdown() {
pref_change_registrar_.RemoveAll();
}
void SecurityTokenSessionController::OnCertificatesUpdated(
const std::string& extension_id,
const std::vector<certificate_provider::CertificateInfo>&
certificate_infos) {
if (behavior_ == Behavior::kIgnore)
return;
if (!observed_extensions_.contains(extension_id))
return;
if (extension_to_spkis_[extension_id].empty())
return;
bool extension_provides_all_required_certificates = true;
std::vector<std::string> provided_spki_vector;
for (auto certificate_info : certificate_infos) {
provided_spki_vector.emplace_back(
GetSubjectPublicKeyInfo(*certificate_info.certificate.get()));
}
base::flat_set<std::string> provided_spkis(provided_spki_vector.begin(),
provided_spki_vector.end());
auto& expected_spkis = extension_to_spkis_[extension_id];
for (const auto& expected_spki : expected_spkis) {
if (!provided_spkis.contains(expected_spki)) {
extension_provides_all_required_certificates = false;
break;
}
}
if (extension_provides_all_required_certificates) {
ExtensionProvidesAllRequiredCertificates(extension_id);
} else {
ExtensionStopsProvidingCertificate(extension_id);
}
}
// static
void SecurityTokenSessionController::RegisterLocalStatePrefs(
PrefRegistrySimple* registry) {
......@@ -166,13 +276,13 @@ void SecurityTokenSessionController::MaybeDisplayLoginScreenNotification() {
// No notification is scheduled.
return;
}
local_state->ClearPref(
prefs::kSecurityTokenSessionNotificationScheduledDomain);
// Sanitize `scheduled_notification_domain`, as values coming from local state
// are not trusted.
std::string domain = scheduled_notification_domain->GetValue()->GetString();
local_state->ClearPref(
prefs::kSecurityTokenSessionNotificationScheduledDomain);
std::string sanitized_domain;
if (!SanitizeDomain(scheduled_notification_domain->GetValue()->GetString(),
sanitized_domain)) {
if (!SanitizeDomain(domain, sanitized_domain)) {
// The pref value is invalid.
return;
}
......@@ -184,7 +294,15 @@ void SecurityTokenSessionController::MaybeDisplayLoginScreenNotification() {
}
void SecurityTokenSessionController::UpdateBehaviorPref() {
Behavior previous_behavior = behavior_;
behavior_ = GetBehaviorFromPref();
if (behavior_ == Behavior::kIgnore) {
Reset();
} else if (previous_behavior == Behavior::kIgnore) {
// Request all available certificates to ensure that all required
// certificates are still present.
certificate_provider_->GetCertificates(base::DoNothing());
}
}
void SecurityTokenSessionController::UpdateNotificationPref() {
......@@ -199,6 +317,60 @@ SecurityTokenSessionController::GetBehaviorFromPref() const {
profile_prefs_->GetString(prefs::kSecurityTokenSessionBehavior));
}
void SecurityTokenSessionController::TriggerAction() {
if (fullscreen_notification_ && !fullscreen_notification_->IsClosed()) {
fullscreen_notification_->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
}
Reset();
switch (behavior_) {
case Behavior::kIgnore:
return;
case Behavior::kLock:
chromeos::ScreenLocker::Show();
AddLockNotification();
return;
case Behavior::kLogout:
chrome::AttemptExit();
ScheduleLogoutNotification();
return;
}
NOTREACHED();
}
void SecurityTokenSessionController::ExtensionProvidesAllRequiredCertificates(
const extensions::ExtensionId& extension_id) {
extensions_missing_required_certificates_.erase(extension_id);
if (extensions_missing_required_certificates_.empty())
Reset();
}
void SecurityTokenSessionController::ExtensionStopsProvidingCertificate(
const extensions::ExtensionId& extension_id) {
extensions_missing_required_certificates_.insert(extension_id);
if (fullscreen_notification_)
// There was already a security token missing.
return;
// Schedule session lock / logout.
action_timer_.Start(
FROM_HERE, notification_seconds_,
base::BindOnce(&SecurityTokenSessionController::TriggerAction,
weak_ptr_factory_.GetWeakPtr()));
if (!notification_seconds_.is_zero()) {
fullscreen_notification_ = views::DialogDelegate::CreateDialogWidget(
std::make_unique<SecurityTokenSessionRestrictionView>(
notification_seconds_,
base::BindOnce(&SecurityTokenSessionController::TriggerAction,
weak_ptr_factory_.GetWeakPtr()),
behavior_, GetEnterpriseDomainFromEmail(user_->GetDisplayEmail())),
nullptr, nullptr);
fullscreen_notification_->Show();
}
}
void SecurityTokenSessionController::AddLockNotification() const {
// A user should see the notification only the first time their session is
// locked.
......@@ -234,5 +406,17 @@ void SecurityTokenSessionController::ScheduleLogoutNotification() const {
GetEnterpriseDomainFromEmail(user_->GetDisplayEmail()));
}
void SecurityTokenSessionController::Reset() {
action_timer_.Stop();
extensions_missing_required_certificates_.clear();
if (fullscreen_notification_) {
if (!fullscreen_notification_->IsClosed()) {
fullscreen_notification_->CloseWithReason(
views::Widget::ClosedReason::kEscKeyPressed);
}
fullscreen_notification_ = nullptr;
}
}
} // namespace login
} // namespace chromeos
......@@ -5,13 +5,29 @@
#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_SECURITY_TOKEN_SESSION_CONTROLLER_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_SECURITY_TOKEN_SESSION_CONTROLLER_H_
#include <string>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "extensions/common/extension_id.h"
namespace views {
class Widget;
}
namespace chromeos {
class CertificateProvider;
namespace login {
// A controller that implements the combined behavior of the
......@@ -21,13 +37,17 @@ namespace login {
// certificate ceases to be present while the user is logged in.
// SecurityTokenSessionNotificationSeconds determines if and how long the user
// is getting informed what is going to happen when the certificate vanishes.
class SecurityTokenSessionController : public KeyedService {
class SecurityTokenSessionController
: public KeyedService,
public CertificateProviderService::Observer {
public:
enum class Behavior { kIgnore, kLogout, kLock };
SecurityTokenSessionController(PrefService* local_state,
PrefService* profile_prefs,
const user_manager::User* user);
SecurityTokenSessionController(
PrefService* local_state,
PrefService* profile_prefs,
const user_manager::User* user,
CertificateProviderService* certificate_provider_service);
SecurityTokenSessionController(const SecurityTokenSessionController& other) =
delete;
SecurityTokenSessionController& operator=(
......@@ -45,13 +65,25 @@ class SecurityTokenSessionController : public KeyedService {
// happens for a user on a device.
static void MaybeDisplayLoginScreenNotification();
// CertificateProviderService::Observer
void OnCertificatesUpdated(
const std::string& extension_id,
const std::vector<certificate_provider::CertificateInfo>&
certificate_infos) override;
private:
Behavior GetBehaviorFromPref() const;
void UpdateBehaviorPref();
void UpdateNotificationPref();
void ExtensionProvidesAllRequiredCertificates(
const extensions::ExtensionId& extension_id);
void ExtensionStopsProvidingCertificate(
const extensions::ExtensionId& extension_id);
void TriggerAction();
void AddLockNotification() const;
void ScheduleLogoutNotification() const;
void Reset();
PrefService* const local_state_;
PrefService* const profile_prefs_;
......@@ -59,6 +91,17 @@ class SecurityTokenSessionController : public KeyedService {
PrefChangeRegistrar pref_change_registrar_;
Behavior behavior_ = Behavior::kIgnore;
base::TimeDelta notification_seconds_;
base::flat_set<extensions::ExtensionId> observed_extensions_;
base::flat_map<extensions::ExtensionId, std::vector<std::string>>
extension_to_spkis_;
base::flat_set<extensions::ExtensionId>
extensions_missing_required_certificates_;
views::Widget* fullscreen_notification_ = nullptr;
base::OneShotTimer action_timer_;
CertificateProviderService* certificate_provider_service_ = nullptr;
std::unique_ptr<CertificateProvider> certificate_provider_;
base::WeakPtrFactory<SecurityTokenSessionController> weak_ptr_factory_{this};
};
} // namespace login
......
......@@ -6,11 +6,13 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/chromeos/login/challenge_response_auth_keys_loader.h"
#include "chrome/browser/chromeos/login/security_token_session_controller.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/user_manager/user.h"
namespace chromeos {
namespace login {
......@@ -52,10 +54,21 @@ KeyedService* SecurityTokenSessionControllerFactory::BuildServiceInstanceFor(
// This can happen in tests that do not have local state.
return nullptr;
}
// The service is only relevant for users who authenticate with a security
// token used for a challenge-response flow.
// TODO(crbug.com/1164373): This check produces false negatives for ephemeral
// users.
user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(
Profile::FromBrowserContext(context));
if (!ChallengeResponseAuthKeysLoader::CanAuthenticateUser(
user->GetAccountId()))
return nullptr;
CertificateProviderService* certificate_provider_service =
CertificateProviderServiceFactory::GetForBrowserContext(context);
return new SecurityTokenSessionController(local_state, profile->GetPrefs(),
user);
user, certificate_provider_service);
}
content::BrowserContext*
......
......@@ -22,6 +22,7 @@
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"
......@@ -112,7 +113,6 @@ base::string16 GetDialogText(
SecurityTokenSessionRestrictionView::SecurityTokenSessionRestrictionView(
base::TimeDelta duration,
base::OnceClosure accept_callback,
base::OnceClosure window_closing_callback,
chromeos::login::SecurityTokenSessionController::Behavior behavior,
const std::string& domain)
: AppDialogView(GetImage()),
......@@ -125,7 +125,6 @@ SecurityTokenSessionRestrictionView::SecurityTokenSessionRestrictionView(
SetTitle(GetTitle(behavior));
SetAcceptCallback(std::move(accept_callback));
RegisterWindowClosingCallback(std::move(window_closing_callback));
InitializeView(/*heading_text=*/base::string16());
UpdateLabel();
......@@ -144,3 +143,6 @@ void SecurityTokenSessionRestrictionView::UpdateLabel() {
update_timer_.Stop();
}
}
BEGIN_METADATA(SecurityTokenSessionRestrictionView, AppDialogView);
END_METADATA
......@@ -10,11 +10,14 @@
#include "base/timer/timer.h"
#include "chrome/browser/chromeos/login/security_token_session_controller.h"
#include "chrome/browser/ui/views/apps/app_dialog/app_dialog_view.h"
#include "ui/views/metadata/metadata_header_macros.h"
// The dialog informing the user they are about to be logged out or locked
// because they removed their security token (smart card).
class SecurityTokenSessionRestrictionView : public AppDialogView {
public:
METADATA_HEADER(SecurityTokenSessionRestrictionView);
// Creates the dialog.
// `duration`: Initial countdown time, will be displayed in seconds.
// `accept_callback`: Callback when the user accepts the dialog.
......@@ -26,7 +29,6 @@ class SecurityTokenSessionRestrictionView : public AppDialogView {
SecurityTokenSessionRestrictionView(
base::TimeDelta duration,
base::OnceClosure accept_callback,
base::OnceClosure window_closing_callback,
chromeos::login::SecurityTokenSessionController::Behavior behavior,
const std::string& domain);
SecurityTokenSessionRestrictionView(
......
......@@ -7679,7 +7679,7 @@
'caption': '''Lock the current session.''',
},
],
'future_on': ['chrome_os'],
'supported_on': ['chrome_os:90-'],
'features': {
'dynamic_refresh': True,
'per_profile': False,
......@@ -7695,7 +7695,7 @@
'owners': ['fabiansommer@chromium.org, emaxx@chromium.org'],
'type': 'int',
'schema': { 'type': 'integer', 'minimum': 0, 'maximum': 9999 },
'future_on': ['chrome_os'],
'supported_on': ['chrome_os:90-'],
'features': {
'dynamic_refresh': True,
'per_profile': False,
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment