Commit 550419f8 authored by Toni Barzic's avatar Toni Barzic Committed by Commit Bot

Show a notification when a detachable base change is detected for a user

Adds DetachableBaseNotificationController that observes
DetachableBaseHandler in order to detect when a detachable base
different than the last base used by the active user changes. When a
detachable base change is detected, it shows a system notification.
The notification serves a security purpose - it warns the user that
the detachable base they are using has changed, and that the base could
be compromised (to steal their keystrokes).

Note that the notification is shown only if the session is unblocked
at the time - lock screen and login screen will implement their own UI
to notify the user about the base change.

If/when the session is unblocked, the attached base is set as the last
one used by the user to prevent the notification from showing up next
time the user attaches the same base.

Bug: 796300
Change-Id: I7ff8fdf3e02e5833df2a709169fdd076285f3a4b
Reviewed-on: https://chromium-review.googlesource.com/952477Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Reviewed-by: default avatarAlexander Alekseev <alemate@chromium.org>
Commit-Queue: Toni Barzic <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#541717}
parent 5e638c8e
...@@ -103,6 +103,8 @@ component("ash") { ...@@ -103,6 +103,8 @@ component("ash") {
"debug.h", "debug.h",
"detachable_base/detachable_base_handler.cc", "detachable_base/detachable_base_handler.cc",
"detachable_base/detachable_base_handler.h", "detachable_base/detachable_base_handler.h",
"detachable_base/detachable_base_notification_controller.cc",
"detachable_base/detachable_base_notification_controller.h",
"detachable_base/detachable_base_observer.h", "detachable_base/detachable_base_observer.h",
"detachable_base/detachable_base_pairing_status.h", "detachable_base/detachable_base_pairing_status.h",
"disconnected_app_handler.cc", "disconnected_app_handler.cc",
...@@ -1411,6 +1413,7 @@ test("ash_unittests") { ...@@ -1411,6 +1413,7 @@ test("ash_unittests") {
"ash_touch_exploration_manager_chromeos_unittest.cc", "ash_touch_exploration_manager_chromeos_unittest.cc",
"autoclick/autoclick_unittest.cc", "autoclick/autoclick_unittest.cc",
"detachable_base/detachable_base_handler_unittest.cc", "detachable_base/detachable_base_handler_unittest.cc",
"detachable_base/detachable_base_notification_controller_unittest.cc",
"dip_unittest.cc", "dip_unittest.cc",
"display/cursor_window_controller_unittest.cc", "display/cursor_window_controller_unittest.cc",
"display/display_color_manager_chromeos_unittest.cc", "display/display_color_manager_chromeos_unittest.cc",
......
// Copyright 2018 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 "ash/detachable_base/detachable_base_notification_controller.h"
#include <memory>
#include <utility>
#include "ash/detachable_base/detachable_base_handler.h"
#include "ash/detachable_base/detachable_base_pairing_status.h"
#include "ash/public/cpp/vector_icons/vector_icons.h"
#include "ash/session/session_controller.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/strings/string16.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
namespace ash {
namespace {
constexpr char kDetachableBaseNotifierId[] = "ash.system.detachable_base";
} // namespace
const char DetachableBaseNotificationController::kBaseChangedNotificationId[] =
"chrome://settings/detachable_base/detachable_base_changed";
DetachableBaseNotificationController::DetachableBaseNotificationController(
DetachableBaseHandler* detachable_base_handler)
: detachable_base_handler_(detachable_base_handler),
detachable_base_observer_(this),
session_observer_(this) {
detachable_base_observer_.Add(detachable_base_handler);
ShowPairingNotificationIfNeeded();
}
DetachableBaseNotificationController::~DetachableBaseNotificationController() =
default;
void DetachableBaseNotificationController::OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus status) {
ShowPairingNotificationIfNeeded();
}
void DetachableBaseNotificationController::OnActiveUserSessionChanged(
const AccountId& account_id) {
// Remove notification shown for the provious user.
RemovePairingNotification();
ShowPairingNotificationIfNeeded();
}
void DetachableBaseNotificationController::OnSessionStateChanged(
session_manager::SessionState state) {
// Remove the existing notification if the session gets blocked - lock UI
// displays its own warning for base changes, when needed.
RemovePairingNotification();
ShowPairingNotificationIfNeeded();
}
void DetachableBaseNotificationController::ShowPairingNotificationIfNeeded() {
// Do not show the notification if the session is blocked - login/lock UI have
// their own UI for notifying the user of the detachable base change.
if (Shell::Get()->session_controller()->IsUserSessionBlocked())
return;
const mojom::UserSession* active_session =
Shell::Get()->session_controller()->GetUserSession(0);
if (!active_session || !active_session->user_info)
return;
DetachableBasePairingStatus pairing_status =
detachable_base_handler_->GetPairingStatus();
if (pairing_status == DetachableBasePairingStatus::kNone)
return;
const mojom::UserInfo& user_info = *active_session->user_info;
if (pairing_status == DetachableBasePairingStatus::kAuthenticated &&
detachable_base_handler_->PairedBaseMatchesLastUsedByUser(user_info)) {
// Set the current base as last used by the user.
// PairedBaseMatchesLastUsedByUser returns true if the user has not
// previously used a base, so make sure the last used base value is actually
// set.
detachable_base_handler_->SetPairedBaseAsLastUsedByUser(user_info);
return;
}
// Remove any previously added notifications to ensure the new notification is
// shown to the user as a pop-up.
RemovePairingNotification();
message_center::RichNotificationData options;
options.never_timeout = true;
options.priority = message_center::MAX_PRIORITY;
base::string16 title = l10n_util::GetStringUTF16(
IDS_ASH_DETACHABLE_BASE_NOTIFICATION_DEVICE_CHANGED_TITLE);
base::string16 message = l10n_util::GetStringUTF16(
IDS_ASH_DETACHABLE_BASE_NOTIFICATION_DEVICE_CHANGED_MESSAGE);
std::unique_ptr<message_center::Notification> notification =
message_center::Notification::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE, kBaseChangedNotificationId,
title, message, gfx::Image(), base::string16(), GURL(),
message_center::NotifierId(
message_center::NotifierId::SYSTEM_COMPONENT,
kDetachableBaseNotifierId),
options, nullptr, kNotificationWarningIcon,
message_center::SystemNotificationWarningLevel::CRITICAL_WARNING);
message_center::MessageCenter::Get()->AddNotification(
std::move(notification));
// At this point the session is unblocked - mark the current base as used by
// user (as they have just been notified about the base change).
if (pairing_status == DetachableBasePairingStatus::kAuthenticated)
detachable_base_handler_->SetPairedBaseAsLastUsedByUser(user_info);
}
void DetachableBaseNotificationController::RemovePairingNotification() {
message_center::MessageCenter::Get()->RemoveNotification(
kBaseChangedNotificationId, false);
}
} // namespace ash
// Copyright 2018 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.
#ifndef ASH_DETACHABLE_BASE_DETACHABLE_BASE_NOTIFICATION_CONTROLLER_H_
#define ASH_DETACHABLE_BASE_DETACHABLE_BASE_NOTIFICATION_CONTROLLER_H_
#include "ash/ash_export.h"
#include "ash/detachable_base/detachable_base_observer.h"
#include "ash/session/session_observer.h"
#include "base/scoped_observer.h"
namespace ash {
class DetachableBaseHandler;
// Observes DetachableBaseHandler to detect changes to detachable base state,
// and shows relevant notifications as needed:
// * when the attached base is different from the last one used by the active
// user, it shows a notification warning the user the base has changed, and
// that the newly attached base might be malicious (untrusted base might be
// tracking the user's key strokes).
// * when the attached base could not be authenticated, it warns the user that
// the base may not be trusted.
class ASH_EXPORT DetachableBaseNotificationController
: public DetachableBaseObserver,
public SessionObserver {
public:
static const char kBaseChangedNotificationId[];
explicit DetachableBaseNotificationController(
DetachableBaseHandler* detachable_base_handler);
~DetachableBaseNotificationController() override;
// DetachableBaseObserver:
void OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus pairing_status) override;
// SessionObserver:
void OnActiveUserSessionChanged(const AccountId& account_id) override;
void OnSessionStateChanged(session_manager::SessionState state) override;
private:
// Called when the session state or detachable base pairing state change.
// Determines whether the current state requires showing a notification to the
// user, and show kBaseChangedNotificationId if that is the case.
void ShowPairingNotificationIfNeeded();
// Removes kBaseChangedNotificationId notification if it was previously shown
// within the current session.
void RemovePairingNotification();
DetachableBaseHandler* detachable_base_handler_;
ScopedObserver<DetachableBaseHandler, DetachableBaseObserver>
detachable_base_observer_;
ScopedSessionObserver session_observer_;
DISALLOW_COPY_AND_ASSIGN(DetachableBaseNotificationController);
};
} // namespace ash
#endif // ASH_DETACHABLE_BASE_DETACHABLE_BASE_NOTIFICATION_CONTROLLER_H_
// Copyright 2018 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 "ash/detachable_base/detachable_base_notification_controller.h"
#include <string>
#include "ash/detachable_base/detachable_base_handler.h"
#include "ash/public/interfaces/user_info.mojom.h"
#include "ash/session/session_controller.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_power_manager_client.h"
#include "components/signin/core/account_id/account_id.h"
#include "components/user_manager/user_type.h"
#include "ui/message_center/message_center.h"
namespace ash {
mojom::UserInfoPtr CreateTestUserInfo(const std::string& user_email) {
auto user_info = mojom::UserInfo::New();
user_info->type = user_manager::USER_TYPE_REGULAR;
user_info->account_id = AccountId::FromUserEmail(user_email);
user_info->display_name = "Test user";
user_info->display_email = user_email;
user_info->is_ephemeral = false;
user_info->is_new_profile = false;
return user_info;
}
class DetachableBaseNotificationControllerTest : public NoSessionAshTestBase {
public:
DetachableBaseNotificationControllerTest() = default;
~DetachableBaseNotificationControllerTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
GetPowerManagerClient()->SetTabletMode(
chromeos::PowerManagerClient::TabletMode::OFF, base::TimeTicks());
}
chromeos::FakePowerManagerClient* GetPowerManagerClient() {
return static_cast<chromeos::FakePowerManagerClient*>(
chromeos::DBusThreadManager::Get()->GetPowerManagerClient());
}
bool IsBaseChangedNotificationVisible() {
return message_center::MessageCenter::Get()->FindVisibleNotificationById(
DetachableBaseNotificationController::kBaseChangedNotificationId);
}
void CloseBaseChangedNotification() {
message_center::MessageCenter::Get()->RemoveNotification(
DetachableBaseNotificationController::kBaseChangedNotificationId,
true /*by_user*/);
}
DetachableBaseHandler* detachable_base_handler() {
return Shell::Get()->detachable_base_handler();
}
SessionController* session_controller() {
return Shell::Get()->session_controller();
}
private:
DISALLOW_COPY_AND_ASSIGN(DetachableBaseNotificationControllerTest);
};
TEST_F(DetachableBaseNotificationControllerTest,
ShowPairingNotificationIfSessionNotBlocked) {
CreateUserSessions(1);
// The first detachable base used by the user - no notification expected.
detachable_base_handler()->PairChallengeSucceeded({0x01, 0x01});
EXPECT_FALSE(IsBaseChangedNotificationVisible());
// If the user changes the paired base in session, the detachable base change
// notification should be shown.
detachable_base_handler()->PairChallengeSucceeded({0x02, 0x02});
EXPECT_TRUE(IsBaseChangedNotificationVisible());
CloseBaseChangedNotification();
EXPECT_FALSE(IsBaseChangedNotificationVisible());
// Verify that the notification is reshown if the base changes again.
detachable_base_handler()->PairChallengeSucceeded({0x03, 0x03});
EXPECT_TRUE(IsBaseChangedNotificationVisible());
}
TEST_F(DetachableBaseNotificationControllerTest,
ShowNotificationOnNonAuthenticatedBases) {
CreateUserSessions(1);
detachable_base_handler()->PairChallengeFailed();
EXPECT_TRUE(IsBaseChangedNotificationVisible());
}
TEST_F(DetachableBaseNotificationControllerTest,
UpdateNotificationOnUserSwitch) {
CreateUserSessions(1);
// The first detachable base used by the user - no notification expected.
detachable_base_handler()->PairChallengeSucceeded({0x01, 0x01});
EXPECT_FALSE(IsBaseChangedNotificationVisible());
SimulateUserLogin("secondary_user@test.com");
EXPECT_FALSE(IsBaseChangedNotificationVisible());
detachable_base_handler()->PairChallengeSucceeded({0x02, 0x02});
EXPECT_TRUE(IsBaseChangedNotificationVisible());
CloseBaseChangedNotification();
GetSessionControllerClient()->SwitchActiveUser(
session_controller()->GetUserSession(1)->user_info->account_id);
EXPECT_TRUE(IsBaseChangedNotificationVisible());
}
TEST_F(DetachableBaseNotificationControllerTest,
NonAuthenticatedBaseNotificationOnUserSwitch) {
CreateUserSessions(1);
detachable_base_handler()->PairChallengeFailed();
EXPECT_TRUE(IsBaseChangedNotificationVisible());
CloseBaseChangedNotification();
SimulateUserLogin("secondary_user@test.com");
EXPECT_TRUE(IsBaseChangedNotificationVisible());
}
TEST_F(DetachableBaseNotificationControllerTest,
NoNotificationIfSessionNotStarted) {
const char kTestUser[] = "user_1@test.com";
mojom::UserInfoPtr test_user_info = CreateTestUserInfo(kTestUser);
// Set a detachable base as previously used by the user before log in.
detachable_base_handler()->PairChallengeSucceeded({0x01, 0x01});
EXPECT_TRUE(detachable_base_handler()->SetPairedBaseAsLastUsedByUser(
*test_user_info));
// Set up another detachable base as attached when the user logs in.
detachable_base_handler()->PairChallengeSucceeded({0x02, 0x02});
// No active user, so the notification should not be shown, yet.
EXPECT_FALSE(IsBaseChangedNotificationVisible());
SimulateUserLogin(kTestUser);
EXPECT_TRUE(IsBaseChangedNotificationVisible());
}
TEST_F(DetachableBaseNotificationControllerTest,
NoNotificationOnSessionStartIfBaseMarkedAsLastUsed) {
const char kTestUser[] = "user_1@test.com";
mojom::UserInfoPtr test_user_info = CreateTestUserInfo(kTestUser);
// Set a detachable base as previously used by the user before log in.
detachable_base_handler()->PairChallengeSucceeded({0x01, 0x01});
EXPECT_TRUE(detachable_base_handler()->SetPairedBaseAsLastUsedByUser(
*test_user_info));
// Set up another detachable base as attached when the user logs in.
detachable_base_handler()->PairChallengeSucceeded({0x02, 0x02});
// No active user, so the notification should not be shown, yet.
EXPECT_FALSE(IsBaseChangedNotificationVisible());
// Mark the current device as last used by the user, and verify there is no
// notification when the user logs in.
EXPECT_TRUE(detachable_base_handler()->SetPairedBaseAsLastUsedByUser(
*test_user_info));
SimulateUserLogin(kTestUser);
EXPECT_FALSE(IsBaseChangedNotificationVisible());
}
// Tests that a notification for non authenticated base is not shown before the
// session is started - the login UI will show a custom UI to inform the user
// about the base.
TEST_F(DetachableBaseNotificationControllerTest,
NonAuthenticatedBaseNotificationNotShownBeforeLogin) {
detachable_base_handler()->PairChallengeFailed();
EXPECT_FALSE(IsBaseChangedNotificationVisible());
CreateUserSessions(1);
EXPECT_TRUE(IsBaseChangedNotificationVisible());
}
TEST_F(DetachableBaseNotificationControllerTest, NoNotificationOnLockScreen) {
CreateUserSessions(1);
// The first detachable base used by the user - no notification expected.
detachable_base_handler()->PairChallengeSucceeded({0x01, 0x01});
BlockUserSession(UserSessionBlockReason::BLOCKED_BY_LOCK_SCREEN);
detachable_base_handler()->PairChallengeSucceeded({0x02, 0x02});
EXPECT_FALSE(IsBaseChangedNotificationVisible());
UnblockUserSession();
EXPECT_TRUE(IsBaseChangedNotificationVisible());
}
TEST_F(DetachableBaseNotificationControllerTest,
NoNotificationAfterLockScreenIfSetAsUsed) {
CreateUserSessions(1);
// The first detachable base used by the user - no notification expected.
detachable_base_handler()->PairChallengeSucceeded({0x01, 0x01});
BlockUserSession(UserSessionBlockReason::BLOCKED_BY_LOCK_SCREEN);
detachable_base_handler()->PairChallengeSucceeded({0x02, 0x02});
EXPECT_FALSE(IsBaseChangedNotificationVisible());
EXPECT_TRUE(detachable_base_handler()->SetPairedBaseAsLastUsedByUser(
*session_controller()->GetUserSession(0)->user_info));
UnblockUserSession();
EXPECT_FALSE(IsBaseChangedNotificationVisible());
}
// Tests that a notification for non authenticated base is not shown before the
// session is started - the lock UI will show a custom UI to inform the user
// about the base.
TEST_F(DetachableBaseNotificationControllerTest,
NonAuthenticatedBaseNotificationNotShownOnLock) {
BlockUserSession(UserSessionBlockReason::BLOCKED_BY_LOCK_SCREEN);
detachable_base_handler()->PairChallengeFailed();
EXPECT_FALSE(IsBaseChangedNotificationVisible());
UnblockUserSession();
EXPECT_TRUE(IsBaseChangedNotificationVisible());
}
} // namespace ash
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "ash/autoclick/autoclick_controller.h" #include "ash/autoclick/autoclick_controller.h"
#include "ash/cast_config_controller.h" #include "ash/cast_config_controller.h"
#include "ash/detachable_base/detachable_base_handler.h" #include "ash/detachable_base/detachable_base_handler.h"
#include "ash/detachable_base/detachable_base_notification_controller.h"
#include "ash/display/ash_display_controller.h" #include "ash/display/ash_display_controller.h"
#include "ash/display/cursor_window_controller.h" #include "ash/display/cursor_window_controller.h"
#include "ash/display/display_color_manager_chromeos.h" #include "ash/display/display_color_manager_chromeos.h"
...@@ -860,6 +861,9 @@ Shell::~Shell() { ...@@ -860,6 +861,9 @@ Shell::~Shell() {
// TouchDevicesController depends on the PrefService and must be destructed // TouchDevicesController depends on the PrefService and must be destructed
// before it. // before it.
touch_devices_controller_ = nullptr; touch_devices_controller_ = nullptr;
// DetachableBaseNotificationController depends on DetachableBaseHandler, and
// has to be destructed before it.
detachable_base_notification_controller_.reset();
// DetachableBaseHandler depends on the PrefService and must be destructed // DetachableBaseHandler depends on the PrefService and must be destructed
// before it. // before it.
detachable_base_handler_.reset(); detachable_base_handler_.reset();
...@@ -885,6 +889,9 @@ void Shell::Init(ui::ContextFactory* context_factory, ...@@ -885,6 +889,9 @@ void Shell::Init(ui::ContextFactory* context_factory,
touch_devices_controller_ = std::make_unique<TouchDevicesController>(); touch_devices_controller_ = std::make_unique<TouchDevicesController>();
bluetooth_power_controller_ = std::make_unique<BluetoothPowerController>(); bluetooth_power_controller_ = std::make_unique<BluetoothPowerController>();
detachable_base_handler_ = std::make_unique<DetachableBaseHandler>(this); detachable_base_handler_ = std::make_unique<DetachableBaseHandler>(this);
detachable_base_notification_controller_ =
std::make_unique<DetachableBaseNotificationController>(
detachable_base_handler_.get());
// Connector can be null in tests. // Connector can be null in tests.
if (shell_delegate_->GetShellConnector()) { if (shell_delegate_->GetShellConnector()) {
......
...@@ -93,6 +93,7 @@ class BluetoothPowerController; ...@@ -93,6 +93,7 @@ class BluetoothPowerController;
class BrightnessControlDelegate; class BrightnessControlDelegate;
class CastConfigController; class CastConfigController;
class DetachableBaseHandler; class DetachableBaseHandler;
class DetachableBaseNotificationController;
class DisplayColorManager; class DisplayColorManager;
class DisplayConfigurationController; class DisplayConfigurationController;
class DisplayErrorObserver; class DisplayErrorObserver;
...@@ -667,6 +668,8 @@ class ASH_EXPORT Shell : public SessionObserver, ...@@ -667,6 +668,8 @@ class ASH_EXPORT Shell : public SessionObserver,
std::unique_ptr<BrightnessControlDelegate> brightness_control_delegate_; std::unique_ptr<BrightnessControlDelegate> brightness_control_delegate_;
std::unique_ptr<CastConfigController> cast_config_; std::unique_ptr<CastConfigController> cast_config_;
std::unique_ptr<DetachableBaseHandler> detachable_base_handler_; std::unique_ptr<DetachableBaseHandler> detachable_base_handler_;
std::unique_ptr<DetachableBaseNotificationController>
detachable_base_notification_controller_;
std::unique_ptr<DragDropController> drag_drop_controller_; std::unique_ptr<DragDropController> drag_drop_controller_;
std::unique_ptr<FocusCycler> focus_cycler_; std::unique_ptr<FocusCycler> focus_cycler_;
std::unique_ptr<ImeController> ime_controller_; std::unique_ptr<ImeController> ime_controller_;
......
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