Commit d2432cb8 authored by Toni Barzic's avatar Toni Barzic Committed by Commit Bot

Add detachable base change notification to login webui

Adds logic to display "persistent" error bubble (i.e. error bubble
that remains shown if user clicks, scrolls, types outside of the
bubble) that warns the user when a detachable base change / invalid
detachable base is detected. The warning warns the user that the
detachable base is different than he one they used last, and that
they should proceed with caution as the base might be malicious.

SigninScreenHandler is updated to observer ash::DetachableBaseHandler
state (currently non-mash only), and notify the UI when the error
message should be shown or hidden as the detachable base pairing
status, and the focused user pod change.

BUG=796300

Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: I2813af6ac9473554509308194b3ae04331d013b9
Reviewed-on: https://chromium-review.googlesource.com/956802
Commit-Queue: Toni Barzic <tbarzic@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543219}
parent 2871be05
...@@ -56,6 +56,10 @@ cr.define('cr.ui.Oobe', function() { ...@@ -56,6 +56,10 @@ cr.define('cr.ui.Oobe', function() {
cr.ui.login.DisplayManager.initialize(); cr.ui.login.DisplayManager.initialize();
login.AccountPickerScreen.register(); login.AccountPickerScreen.register();
cr.ui.Bubble.decorate($('bubble-persistent'));
$('bubble-persistent').persistent = true;
$('bubble-persistent').hideOnKeyPress = false;
cr.ui.Bubble.decorate($('bubble')); cr.ui.Bubble.decorate($('bubble'));
login.HeaderBar.decorate($('login-header-bar')); login.HeaderBar.decorate($('login-header-bar'));
login.TopHeaderBar.decorate($('top-header-bar')); login.TopHeaderBar.decorate($('top-header-bar'));
......
...@@ -58,6 +58,10 @@ cr.define('cr.ui.Oobe', function() { ...@@ -58,6 +58,10 @@ cr.define('cr.ui.Oobe', function() {
login.UpdateRequiredScreen.register(); login.UpdateRequiredScreen.register();
login.DemoSetupScreen.register(); login.DemoSetupScreen.register();
cr.ui.Bubble.decorate($('bubble-persistent'));
$('bubble-persistent').persistent = true;
$('bubble-persistent').hideOnKeyPress = false;
cr.ui.Bubble.decorate($('bubble')); cr.ui.Bubble.decorate($('bubble'));
login.HeaderBar.decorate($('login-header-bar')); login.HeaderBar.decorate($('login-header-bar'));
login.TopHeaderBar.decorate($('top-header-bar')); login.TopHeaderBar.decorate($('top-header-bar'));
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
</div> </div>
</div> </div>
</div> </div>
<div id="bubble-persistent" class="bubble faded" hidden></div>
<div id="bubble" class="bubble faded" hidden></div> <div id="bubble" class="bubble faded" hidden></div>
<include src="md_top_header_bar.html"> <include src="md_top_header_bar.html">
<include src="md_header_bar.html"> <include src="md_header_bar.html">
......
...@@ -59,6 +59,10 @@ cr.define('cr.ui.Oobe', function() { ...@@ -59,6 +59,10 @@ cr.define('cr.ui.Oobe', function() {
login.WaitForContainerReadyScreen.register(); login.WaitForContainerReadyScreen.register();
login.DemoSetupScreen.register(); login.DemoSetupScreen.register();
cr.ui.Bubble.decorate($('bubble-persistent'));
$('bubble-persistent').persistent = true;
$('bubble-persistent').hideOnKeyPress = false;
cr.ui.Bubble.decorate($('bubble')); cr.ui.Bubble.decorate($('bubble'));
login.HeaderBar.decorate($('login-header-bar')); login.HeaderBar.decorate($('login-header-bar'));
if ($('top-header-bar')) if ($('top-header-bar'))
......
...@@ -25,10 +25,12 @@ specific_include_rules = { ...@@ -25,10 +25,12 @@ specific_include_rules = {
"+ui/events/devices/device_data_manager.h", "+ui/events/devices/device_data_manager.h",
], ],
"signin_screen_handler\.cc": [ "signin_screen_handler\.cc": [
"+ash/detachable_base",
"+ash/shell.h", "+ash/shell.h",
"+ash/wallpaper/wallpaper_controller.h", "+ash/wallpaper/wallpaper_controller.h",
], ],
"signin_screen_handler\.h": [ "signin_screen_handler\.h": [
"+ash/detachable_base/detachable_base_observer.h",
"+ash/wallpaper/wallpaper_controller_observer.h", "+ash/wallpaper/wallpaper_controller_observer.h",
], ],
"signin_userlist_unittest\.cc": [ "signin_userlist_unittest\.cc": [
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "ash/detachable_base/detachable_base_handler.h"
#include "ash/public/cpp/login_constants.h" #include "ash/public/cpp/login_constants.h"
#include "ash/public/interfaces/constants.mojom.h" #include "ash/public/interfaces/constants.mojom.h"
#include "ash/public/interfaces/shutdown.mojom.h" #include "ash/public/interfaces/shutdown.mojom.h"
...@@ -91,6 +92,7 @@ ...@@ -91,6 +92,7 @@
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h" #include "components/prefs/scoped_user_pref_update.h"
#include "components/proximity_auth/screenlock_bridge.h" #include "components/proximity_auth/screenlock_bridge.h"
#include "components/session_manager/core/session_manager.h"
#include "components/strings/grit/components_strings.h" #include "components/strings/grit/components_strings.h"
#include "components/user_manager/known_user.h" #include "components/user_manager/known_user.h"
#include "components/user_manager/user.h" #include "components/user_manager/user.h"
...@@ -107,6 +109,7 @@ ...@@ -107,6 +109,7 @@
#include "ui/base/ime/chromeos/input_method_descriptor.h" #include "ui/base/ime/chromeos/input_method_descriptor.h"
#include "ui/base/ime/chromeos/input_method_manager.h" #include "ui/base/ime/chromeos/input_method_manager.h"
#include "ui/base/ime/chromeos/input_method_util.h" #include "ui/base/ime/chromeos/input_method_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h" #include "ui/base/webui/web_ui_util.h"
#include "ui/chromeos/devicetype_utils.h" #include "ui/chromeos/devicetype_utils.h"
#include "ui/gfx/color_analysis.h" #include "ui/gfx/color_analysis.h"
...@@ -218,6 +221,23 @@ std::string GetNetworkName(const std::string& service_path) { ...@@ -218,6 +221,23 @@ std::string GetNetworkName(const std::string& service_path) {
return network->name(); return network->name();
} }
ash::mojom::UserInfoPtr GetUserInfoForAccount(const AccountId& account_id) {
const user_manager::User* user =
user_manager::UserManager::Get()->FindUser(account_id);
if (!user)
return nullptr;
auto user_info = ash::mojom::UserInfo::New();
user_info->type = user->GetType();
user_info->account_id = account_id;
user_info->is_ephemeral =
user_manager::UserManager::Get()->IsUserNonCryptohomeDataEphemeral(
account_id);
user_info->display_name = base::UTF16ToUTF8(user->display_name());
user_info->display_email = user->display_email();
return user_info;
}
} // namespace } // namespace
// LoginScreenContext implementation ------------------------------------------ // LoginScreenContext implementation ------------------------------------------
...@@ -258,7 +278,9 @@ SigninScreenHandler::SigninScreenHandler( ...@@ -258,7 +278,9 @@ SigninScreenHandler::SigninScreenHandler(
proxy_auth_dialog_reload_times_(kMaxGaiaReloadForProxyAuthDialog), proxy_auth_dialog_reload_times_(kMaxGaiaReloadForProxyAuthDialog),
gaia_screen_handler_(gaia_screen_handler), gaia_screen_handler_(gaia_screen_handler),
histogram_helper_(new ErrorScreensHistogramHelper("Signin")), histogram_helper_(new ErrorScreensHistogramHelper("Signin")),
session_manager_observer_(this),
lock_screen_apps_observer_(this), lock_screen_apps_observer_(this),
detachable_base_observer_(this),
weak_factory_(this) { weak_factory_(this) {
DCHECK(network_state_informer_.get()); DCHECK(network_state_informer_.get());
DCHECK(error_screen_); DCHECK(error_screen_);
...@@ -294,11 +316,16 @@ SigninScreenHandler::SigninScreenHandler( ...@@ -294,11 +316,16 @@ SigninScreenHandler::SigninScreenHandler(
tablet_mode_client->AddObserver(this); tablet_mode_client->AddObserver(this);
OnTabletModeToggled(tablet_mode_client->tablet_mode_enabled()); OnTabletModeToggled(tablet_mode_client->tablet_mode_enabled());
session_manager_observer_.Add(session_manager::SessionManager::Get());
if (lock_screen_apps::StateController::IsEnabled()) if (lock_screen_apps::StateController::IsEnabled())
lock_screen_apps_observer_.Add(lock_screen_apps::StateController::Get()); lock_screen_apps_observer_.Add(lock_screen_apps::StateController::Get());
// TODO(wzang): Make this work under mash. // TODO(wzang): Make this work under mash.
if (GetAshConfig() != ash::Config::MASH) if (GetAshConfig() != ash::Config::MASH)
ash::Shell::Get()->wallpaper_controller()->AddObserver(this); ash::Shell::Get()->wallpaper_controller()->AddObserver(this);
// TODO(tbarzic): This is needed for login UI - remove it when login switches
// to views implementation (or otherwise, make it work under mash).
if (GetAshConfig() != ash::Config::MASH)
detachable_base_observer_.Add(ash::Shell::Get()->detachable_base_handler());
} }
SigninScreenHandler::~SigninScreenHandler() { SigninScreenHandler::~SigninScreenHandler() {
...@@ -1132,6 +1159,34 @@ void SigninScreenHandler::OnTabletModeToggled(bool enabled) { ...@@ -1132,6 +1159,34 @@ void SigninScreenHandler::OnTabletModeToggled(bool enabled) {
CallJSOrDefer("login.AccountPickerScreen.setTabletModeState", enabled); CallJSOrDefer("login.AccountPickerScreen.setTabletModeState", enabled);
} }
void SigninScreenHandler::OnSessionStateChanged() {
// If the session got unblocked, and the user for which the detachable base
// change notification was shown got added to the session, mark the paired
// base as used by the user, so they don't get further notifications about
// the detachable base change.
// The fact the user got added to the session implies that they have
// authenticated while the warning was displayed, so they should be aware
// of the base change at this point.
if (!account_with_detachable_base_error_.has_value())
return;
if (session_manager::SessionManager::Get()->IsUserSessionBlocked())
return;
const AccountId& account_id = *account_with_detachable_base_error_;
if (session_manager::SessionManager::Get()->HasSessionForAccountId(
account_id)) {
ash::mojom::UserInfoPtr user_info = GetUserInfoForAccount(account_id);
if (user_info) {
ash::Shell::Get()
->detachable_base_handler()
->SetPairedBaseAsLastUsedByUser(*user_info);
}
}
HideDetachableBaseChangedError();
}
void SigninScreenHandler::OnLockScreenNoteStateChanged( void SigninScreenHandler::OnLockScreenNoteStateChanged(
ash::mojom::TrayActionState state) { ash::mojom::TrayActionState state) {
if (!ScreenLocker::default_screen_locker()) if (!ScreenLocker::default_screen_locker())
...@@ -1441,6 +1496,12 @@ void SigninScreenHandler::HandleLoginUIStateChanged(const std::string& source, ...@@ -1441,6 +1496,12 @@ void SigninScreenHandler::HandleLoginUIStateChanged(const std::string& source,
ui_state_ = UI_STATE_GAIA_SIGNIN; ui_state_ = UI_STATE_GAIA_SIGNIN;
} else if (source == kSourceAccountPicker) { } else if (source == kSourceAccountPicker) {
ui_state_ = UI_STATE_ACCOUNT_PICKER; ui_state_ = UI_STATE_ACCOUNT_PICKER;
if (active) {
UpdateDetachableBaseChangedError();
} else {
HideDetachableBaseChangedError();
}
} else { } else {
NOTREACHED(); NOTREACHED();
return; return;
...@@ -1458,7 +1519,7 @@ void SigninScreenHandler::HandleShowLoadingTimeoutError() { ...@@ -1458,7 +1519,7 @@ void SigninScreenHandler::HandleShowLoadingTimeoutError() {
} }
void SigninScreenHandler::HandleFocusPod(const AccountId& account_id, void SigninScreenHandler::HandleFocusPod(const AccountId& account_id,
bool load_wallpaper) { bool is_large_pod) {
proximity_auth::ScreenlockBridge::Get()->SetFocusedUser(account_id); proximity_auth::ScreenlockBridge::Get()->SetFocusedUser(account_id);
if (delegate_) if (delegate_)
delegate_->CheckUserStatus(account_id); delegate_->CheckUserStatus(account_id);
...@@ -1476,7 +1537,7 @@ void SigninScreenHandler::HandleFocusPod(const AccountId& account_id, ...@@ -1476,7 +1537,7 @@ void SigninScreenHandler::HandleFocusPod(const AccountId& account_id,
lock_screen_utils::SetUserInputMethod(account_id.GetUserEmail(), lock_screen_utils::SetUserInputMethod(account_id.GetUserEmail(),
ime_state_.get()); ime_state_.get());
lock_screen_utils::SetKeyboardSettings(account_id); lock_screen_utils::SetKeyboardSettings(account_id);
if (LoginDisplayHost::default_host() && load_wallpaper) if (LoginDisplayHost::default_host() && is_large_pod)
LoginDisplayHost::default_host()->LoadWallpaper(account_id); LoginDisplayHost::default_host()->LoadWallpaper(account_id);
bool use_24hour_clock = false; bool use_24hour_clock = false;
...@@ -1487,6 +1548,12 @@ void SigninScreenHandler::HandleFocusPod(const AccountId& account_id, ...@@ -1487,6 +1548,12 @@ void SigninScreenHandler::HandleFocusPod(const AccountId& account_id,
->SetLastFocusedPodHourClockType( ->SetLastFocusedPodHourClockType(
use_24hour_clock ? base::k24HourClock : base::k12HourClock); use_24hour_clock ? base::k24HourClock : base::k12HourClock);
} }
// Update the detachable base change warning visibility when the focused
// user pod changes. Note that this should only be done for large pods - the
// pods whose authentication method is shown in the sign-in UI.
if (is_large_pod)
UpdateDetachableBaseChangedError();
} }
} }
...@@ -1696,4 +1763,77 @@ void SigninScreenHandler::OnAllowedInputMethodsChanged() { ...@@ -1696,4 +1763,77 @@ void SigninScreenHandler::OnAllowedInputMethodsChanged() {
} }
} }
void SigninScreenHandler::OnDetachableBasePairingStatusChanged(
ash::DetachableBasePairingStatus status) {
UpdateDetachableBaseChangedError();
}
void SigninScreenHandler::OnDetachableBaseRequiresUpdateChanged(
bool requires_update) {}
void SigninScreenHandler::UpdateDetachableBaseChangedError() {
if (GetAshConfig() == ash::Config::MASH)
return;
auto pairing_status =
ash::Shell::Get()->detachable_base_handler()->GetPairingStatus();
if (pairing_status == ash::DetachableBasePairingStatus::kNone) {
HideDetachableBaseChangedError();
return;
}
// Requests to update the notification state will be postponed until a pod
// gets focused. Reasons for that are:
// * The warning bubble is anchored at a user pod authentication element,
// which is only shown when the pod is focused.
// * If two large pods are shown, it's unclear which one should be
// considered active if neither is focused.
// Send a request to the login UI to select/focus a use pod so the warning can
// be shown sooner, rather than later - the user might start typing without
// focusing a pod first, in which case showing the warning as the pod gets
// focused might be too late to warn the user their keyboard might not be
// trusted.
if (!focused_pod_account_id_) {
CallJSOrDefer(
"login.AccountPickerScreen.selectPodForDetachableBaseWarningBubble");
return;
}
bool base_trusted =
pairing_status == ash::DetachableBasePairingStatus::kAuthenticated;
if (base_trusted) {
ash::mojom::UserInfoPtr user_info =
GetUserInfoForAccount(*focused_pod_account_id_);
if (user_info) {
base_trusted = ash::Shell::Get()
->detachable_base_handler()
->PairedBaseMatchesLastUsedByUser(*user_info);
}
}
if (base_trusted) {
HideDetachableBaseChangedError();
} else {
ShowDetachableBaseChangedError();
}
}
void SigninScreenHandler::ShowDetachableBaseChangedError() {
account_with_detachable_base_error_ = *focused_pod_account_id_;
CallJSOrDefer(
"cr.ui.login.DisplayManager.showDetachableBaseChangedWarning",
*focused_pod_account_id_,
l10n_util::GetStringUTF8(IDS_LOGIN_ERROR_DETACHABLE_BASE_CHANGED),
std::string(), 0);
}
void SigninScreenHandler::HideDetachableBaseChangedError() {
if (!account_with_detachable_base_error_.has_value())
return;
CallJSOrDefer("cr.ui.login.DisplayManager.hideDetachableBaseChangedWarning",
*account_with_detachable_base_error_);
account_with_detachable_base_error_ = base::nullopt;
}
} // namespace chromeos } // namespace chromeos
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <set> #include <set>
#include <string> #include <string>
#include "ash/detachable_base/detachable_base_observer.h"
#include "ash/wallpaper/wallpaper_controller_observer.h" #include "ash/wallpaper/wallpaper_controller_observer.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
...@@ -17,6 +18,7 @@ ...@@ -17,6 +18,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/scoped_observer.h" #include "base/scoped_observer.h"
#include "chrome/browser/chromeos/lock_screen_apps/state_observer.h" #include "chrome/browser/chromeos/lock_screen_apps/state_observer.h"
#include "chrome/browser/chromeos/login/screens/error_screen.h" #include "chrome/browser/chromeos/login/screens/error_screen.h"
...@@ -30,6 +32,7 @@ ...@@ -30,6 +32,7 @@
#include "chromeos/dbus/power_manager_client.h" #include "chromeos/dbus/power_manager_client.h"
#include "chromeos/network/portal_detector/network_portal_detector.h" #include "chromeos/network/portal_detector/network_portal_detector.h"
#include "components/proximity_auth/screenlock_bridge.h" #include "components/proximity_auth/screenlock_bridge.h"
#include "components/session_manager/core/session_manager_observer.h"
#include "components/user_manager/user_manager.h" #include "components/user_manager/user_manager.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_registrar.h"
...@@ -44,8 +47,10 @@ class AccountId; ...@@ -44,8 +47,10 @@ class AccountId;
namespace ash { namespace ash {
namespace mojom { namespace mojom {
enum class TrayActionState; enum class TrayActionState;
} } // namespace mojom
}
class DetachableBaseHandler;
} // namespace ash
namespace base { namespace base {
class DictionaryValue; class DictionaryValue;
...@@ -56,6 +61,10 @@ namespace lock_screen_apps { ...@@ -56,6 +61,10 @@ namespace lock_screen_apps {
class StateController; class StateController;
} }
namespace session_manager {
class SessionManager;
}
namespace chromeos { namespace chromeos {
class CoreOobeView; class CoreOobeView;
...@@ -219,7 +228,9 @@ class SigninScreenHandler ...@@ -219,7 +228,9 @@ class SigninScreenHandler
public TabletModeClientObserver, public TabletModeClientObserver,
public lock_screen_apps::StateObserver, public lock_screen_apps::StateObserver,
public OobeUI::Observer, public OobeUI::Observer,
public ash::WallpaperControllerObserver { public session_manager::SessionManagerObserver,
public ash::WallpaperControllerObserver,
public ash::DetachableBaseObserver {
public: public:
SigninScreenHandler( SigninScreenHandler(
const scoped_refptr<NetworkStateInformer>& network_state_informer, const scoped_refptr<NetworkStateInformer>& network_state_informer,
...@@ -263,6 +274,11 @@ class SigninScreenHandler ...@@ -263,6 +274,11 @@ class SigninScreenHandler
void OnWallpaperColorsChanged() override; void OnWallpaperColorsChanged() override;
void OnWallpaperBlurChanged() override; void OnWallpaperBlurChanged() override;
// ash::DetachableBaseObserver:
void OnDetachableBasePairingStatusChanged(
ash::DetachableBasePairingStatus pairing_status) override;
void OnDetachableBaseRequiresUpdateChanged(bool requires_update) override;
void SetFocusPODCallbackForTesting(base::Closure callback); void SetFocusPODCallbackForTesting(base::Closure callback);
// To avoid spurious error messages on flaky networks, the offline message is // To avoid spurious error messages on flaky networks, the offline message is
...@@ -349,6 +365,9 @@ class SigninScreenHandler ...@@ -349,6 +365,9 @@ class SigninScreenHandler
// TabletModeClientObserver: // TabletModeClientObserver:
void OnTabletModeToggled(bool enabled) override; void OnTabletModeToggled(bool enabled) override;
// session_manager::SessionManagerObserver:
void OnSessionStateChanged() override;
// lock_screen_apps::StateObserver: // lock_screen_apps::StateObserver:
void OnLockScreenNoteStateChanged(ash::mojom::TrayActionState state) override; void OnLockScreenNoteStateChanged(ash::mojom::TrayActionState state) override;
...@@ -395,7 +414,7 @@ class SigninScreenHandler ...@@ -395,7 +414,7 @@ class SigninScreenHandler
void HandleLoginScreenUpdate(); void HandleLoginScreenUpdate();
void HandleShowLoadingTimeoutError(); void HandleShowLoadingTimeoutError();
void HandleShowSupervisedUserCreationScreen(); void HandleShowSupervisedUserCreationScreen();
void HandleFocusPod(const AccountId& account_id, bool load_wallpaper); void HandleFocusPod(const AccountId& account_id, bool is_large_pod);
void HandleNoPodFocused(); void HandleNoPodFocused();
void HandleHardlockPod(const std::string& user_id); void HandleHardlockPod(const std::string& user_id);
void HandleLaunchKioskApp(const AccountId& app_account_id, void HandleLaunchKioskApp(const AccountId& app_account_id,
...@@ -465,6 +484,23 @@ class SigninScreenHandler ...@@ -465,6 +484,23 @@ class SigninScreenHandler
// responding to network state notifications. // responding to network state notifications.
void ReenableNetworkStateUpdatesAfterProxyAuth(); void ReenableNetworkStateUpdatesAfterProxyAuth();
// Determines whether a warning about the detachable base getting changed
// should be shown to the user. The warning is shown a detachable base is
// present, and the user whose pod is currently focused has used a different
// base last time. It updates the detachable base warning visibility as
// required.
void UpdateDetachableBaseChangedError();
// Sends a request to the UI to show a detachable base change warning for the
// currently focused user pod. The warning warns the user that the currently
// attached base is different than the one they last used, and that it might
// not be trusted.
void ShowDetachableBaseChangedError();
// If a detachable base change warning was requested to be shown, sends a
// request to UI to hide the warning.
void HideDetachableBaseChangedError();
// Current UI state of the signin screen. // Current UI state of the signin screen.
UIState ui_state_ = UI_STATE_UNKNOWN; UIState ui_state_ = UI_STATE_UNKNOWN;
...@@ -547,10 +583,20 @@ class SigninScreenHandler ...@@ -547,10 +583,20 @@ class SigninScreenHandler
std::unique_ptr<AccountId> focused_pod_account_id_; std::unique_ptr<AccountId> focused_pod_account_id_;
// If set, the account for which detachable base change warning was shown in
// the login UI.
base::Optional<AccountId> account_with_detachable_base_error_;
ScopedObserver<session_manager::SessionManager,
session_manager::SessionManagerObserver>
session_manager_observer_;
ScopedObserver<lock_screen_apps::StateController, ScopedObserver<lock_screen_apps::StateController,
lock_screen_apps::StateObserver> lock_screen_apps::StateObserver>
lock_screen_apps_observer_; lock_screen_apps_observer_;
ScopedObserver<ash::DetachableBaseHandler, ash::DetachableBaseObserver>
detachable_base_observer_;
base::WeakPtrFactory<SigninScreenHandler> weak_factory_; base::WeakPtrFactory<SigninScreenHandler> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(SigninScreenHandler); DISALLOW_COPY_AND_ASSIGN(SigninScreenHandler);
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
transition: width 180ms ease, height 180ms ease; transition: width 180ms ease, height 180ms ease;
} }
#bubble { #bubble,
#bubble-persistent {
margin-top: 16px; margin-top: 16px;
z-index: 1; z-index: 1;
} }
...@@ -117,4 +118,4 @@ html[screen=lock] #signin-banner.message-set { ...@@ -117,4 +118,4 @@ html[screen=lock] #signin-banner.message-set {
.small-pod-container-mask.rotate { .small-pod-container-mask.rotate {
transform: rotate(180deg); transform: rotate(180deg);
} }
\ No newline at end of file
...@@ -14,6 +14,14 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() { ...@@ -14,6 +14,14 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() {
*/ */
var MAX_LOGIN_ATTEMPTS_IN_POD = 3; var MAX_LOGIN_ATTEMPTS_IN_POD = 3;
/**
* Time after which the sign-in error bubble should be hidden if it is
* overlayed over the detachable base change warning bubble (to ensure that
* the detachable base warning is not obscured indefinitely).
* @const {number}
*/
var SIGNIN_ERROR_OVER_DETACHABLE_BASE_WARNING_TIMEOUT_MS = 5000;
return { return {
EXTERNAL_API: [ EXTERNAL_API: [
'loadUsers', 'loadUsers',
...@@ -30,6 +38,7 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() { ...@@ -30,6 +38,7 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() {
'hideUserPodCustomIcon', 'hideUserPodCustomIcon',
'setUserPodFingerprintIcon', 'setUserPodFingerprintIcon',
'removeUserPodFingerprintIcon', 'removeUserPodFingerprintIcon',
'selectPodForDetachableBaseWarningBubble',
'setPinEnabledForUser', 'setPinEnabledForUser',
'setAuthType', 'setAuthType',
'setTabletModeState', 'setTabletModeState',
...@@ -173,6 +182,7 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() { ...@@ -173,6 +182,7 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() {
*/ */
onBeforeHide: function() { onBeforeHide: function() {
$('pod-row').clearFocusedPod(); $('pod-row').clearFocusedPod();
$('bubble-persistent').hide();
this.showing_ = false; this.showing_ = false;
chrome.send('loginUIStateChanged', ['account-picker', false]); chrome.send('loginUIStateChanged', ['account-picker', false]);
$('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN; $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
...@@ -205,10 +215,65 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() { ...@@ -205,10 +215,65 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() {
} }
// Update the pod row display if incorrect password. // Update the pod row display if incorrect password.
$('pod-row').setFocusedPodErrorDisplay(true); $('pod-row').setFocusedPodErrorDisplay(true);
activatedPod.showBubble(error);
// If a warning that the detachable base is different than the one
// previously used by the user is shown for the pod, make sure that the
// sign-in error gets hidden reasonably soon.
// If the detachable base was changed maliciously while the user was
// away, the attacker might attempt to use the sign-in error but to
// obscure the detachable base warning hoping that the user will miss it
// when they get back to the device.
var timeout = activatedPod.showingDetachableBaseWarningBubble() ?
SIGNIN_ERROR_OVER_DETACHABLE_BASE_WARNING_TIMEOUT_MS :
undefined;
activatedPod.showBubble(error, {timeout: timeout});
} }
}, },
/**
* Ensures that a user pod is selected and focused, and thus ready to show a
* warning bubble for detachable base change. This is needed for two
* reasons:
* 1. The detachable base state is associated with a user, so a user pod
* has to be selected in order to know for which user the detachable
* base state should be considered (e.g. there might be two large user
* pods in the account picker).
* 2. The warning bubble is attached to the pod's auth element, which is
* only shown if the pod is focused. The bubble anchor should be
* visible in order to properly calculate the bubble position.
*/
selectPodForDetachableBaseWarningBubble: function() {
$('pod-row').maybePreselectPod();
},
/**
* Shows a persistent bubble warning to the user that the current detachable
* base is different than the one they were last using, and that it might
* not be trusted.
*
* @param {string} username The username of the user under whose user pod
* the warning should be displayed.
* @param {HTMLElement} content The warning bubble content.
*/
showDetachableBaseWarningBubble: function(username, content) {
var podRow = $('pod-row');
var pod = podRow.pods.find(pod => pod.user.username == username);
if (pod)
pod.showDetachableBaseWarningBubble(content);
},
/**
* Hides the detachable base warning for the user.
*
* @param {string} username The username that identifies the user pod from
* under which the detachable base warning bubble should be removed.
*/
hideDetachableBaseWarningBubble: function(username) {
var pod = $('pod-row').pods.find(pod => pod.user.username == username);
if (pod)
pod.hideDetachableBaseWarningBubble();
},
/** /**
* Loads given users in pod row. * Loads given users in pod row.
* @param {array} users Array of user. * @param {array} users Array of user.
......
...@@ -777,6 +777,15 @@ cr.define('login', function() { ...@@ -777,6 +777,15 @@ cr.define('login', function() {
*/ */
pinEnabled: false, pinEnabled: false,
/**
* If set, a function which hides a persistent detachable base warning
* bubble. This will be set if a detachable base warning bubble is shown for
* this pod.
* @type {?function()}
* @private
*/
detachableBaseWarningBubbleHider_: null,
/** @override */ /** @override */
decorate: function() { decorate: function() {
this.tabIndex = UserPodTabOrder.POD_INPUT; this.tabIndex = UserPodTabOrder.POD_INPUT;
...@@ -1673,31 +1682,65 @@ cr.define('login', function() { ...@@ -1673,31 +1682,65 @@ cr.define('login', function() {
} }
}, },
/**
* Returns the element that should be used as the anchor for error bubbles
* associated with the pod.
*
* @return {HTMLElement} The anchor for error bubbles.
* @private
*/
getBubbleAnchor_: function() {
var bubbleAnchor = this.getElementsByClassName('auth-container')[0];
if (!bubbleAnchor) {
console.error('auth-container not found!');
bubbleAnchor = this.mainInput;
}
return bubbleAnchor;
},
/** /**
* Shows a bubble under the auth-container of the user pod. * Shows a bubble under the auth-container of the user pod.
* @param {HTMLElement} content Content to show in bubble. * @param {HTMLElement} content Content to show in bubble.
*/ * @param {!{bubble: (HTMLElement|undefined),
showBubble: function(content) { * anchor: (HTMLElement|undefined),
* timeout: (number|undefined)}|undefined} opt_options The custom
* options describing how the bubble should be shown:
* <ul>
* <li>bubble: The element that hosts the bubble content.</li>
* <li>
* anchor: The element to which the bubble should be anchored.
* </li>
* <li>
* timeout: Amount of time in ms after which the bubble
* should be hidden. Note: this should only be used for
* {@code $('bubble')} bubble element. The timeout will get
* cleared if the bubble is shown again.
* </li>
* </ul>
* @return {function()} Function that, when called, hides the shown bubble.
*/
showBubble: function(content, opt_options) {
/** @const */ var BUBBLE_OFFSET = 25; /** @const */ var BUBBLE_OFFSET = 25;
// -8 = 4(BUBBLE_POD_OFFSET) - 2(bubble margin) // -8 = 4(BUBBLE_POD_OFFSET) - 2(bubble margin)
// - 10(internal bubble adjustment) // - 10(internal bubble adjustment)
var bubblePositioningPadding = -8; var bubblePositioningPadding = -8;
var bubbleAnchor; var options = opt_options || {};
var attachment; var bubble = options.bubble || $('bubble');
// Anchor the bubble to the input field.
bubbleAnchor = this.getElementsByClassName('auth-container')[0]; // Make sure bubble timeout is changed only for $('bubble') element.
if (!bubbleAnchor) { if (options.timeout && bubble != $('bubble')) {
console.error('auth-container not found!'); console.error('Timeout can be set only when showing #bubble element.');
bubbleAnchor = this.mainInput; return;
} }
var bubbleAnchor = options.anchor || this.getBubbleAnchor_();
var attachment;
if (this.pinContainer && this.pinContainer.style.visibility == 'visible') if (this.pinContainer && this.pinContainer.style.visibility == 'visible')
attachment = cr.ui.Bubble.Attachment.RIGHT; attachment = cr.ui.Bubble.Attachment.RIGHT;
else else
attachment = cr.ui.Bubble.Attachment.BOTTOM; attachment = cr.ui.Bubble.Attachment.BOTTOM;
var bubble = $('bubble');
// Cannot use cr.ui.LoginUITools.get* on bubble until it is attached to // Cannot use cr.ui.LoginUITools.get* on bubble until it is attached to
// the element. getMaxHeight/Width rely on the correct up/left element // the element. getMaxHeight/Width rely on the correct up/left element
// side positioning that doesn't happen until bubble is attached. // side positioning that doesn't happen until bubble is attached.
...@@ -1734,14 +1777,89 @@ cr.define('login', function() { ...@@ -1734,14 +1777,89 @@ cr.define('login', function() {
attachment = cr.ui.Bubble.Attachment.LEFT; attachment = cr.ui.Bubble.Attachment.LEFT;
} }
} }
if (bubble == $('bubble'))
this.clearBubbleHideTimeout_();
var state = {shown: false, hidden: false};
var showBubbleCallback = function() { var showBubbleCallback = function() {
this.removeEventListener('transitionend', showBubbleCallback); this.removeEventListener('transitionend', showBubbleCallback);
$('bubble').showContentForElement( // If the bubble was requested to be hidden while the transition was in
// progress, do not show the bubble.
if (state.hidden)
return;
state.shown = true;
bubble.showContentForElement(
bubbleAnchor, attachment, content, BUBBLE_OFFSET, bubbleAnchor, attachment, content, BUBBLE_OFFSET,
bubblePositioningPadding, true); bubblePositioningPadding, true);
};
if (options.timeout != undefined) {
this.hideBubbleTimeout_ = setTimeout(() => {
this.hideBubbleTimeout_ = undefined;
bubble.hideForElement(bubbleAnchor);
}, options.timeout);
}
}.bind(this);
this.addEventListener('transitionend', showBubbleCallback); this.addEventListener('transitionend', showBubbleCallback);
ensureTransitionEndEvent(this); ensureTransitionEndEvent(this);
return function() {
if (state.hidden)
return;
state.hidden = true;
if (state.shown)
bubble.hideForElement(bubbleAnchor);
};
},
/**
* Clears the timeout to hide a bubble, if a bubble timeout was set.
* @private
*/
clearBubbleHideTimeout_: function() {
if (this.hideBubbleTimeout_) {
clearTimeout(this.hideBubbleTimeout_);
this.hideBubbleTimeout_ = null;
}
},
/**
* Shows persistent bubble for detachable base change warning.
* @param {HTMLElement} content The bubble contens.
*/
showDetachableBaseWarningBubble: function(content) {
var anchor = this.getBubbleAnchor_();
if (!anchor)
return;
this.clearBubbleHideTimeout_();
$('bubble').hideForElement(anchor);
this.detachableBaseWarningBubbleHider_ = this.showBubble(
content, {bubble: $('bubble-persistent'), anchor: anchor});
},
/**
* If a peristent bubble for detachable base change warning is shown (and
* anchored at this pod), hides the bubble.
*/
hideDetachableBaseWarningBubble: function() {
if (this.detachableBaseWarningBubbleHider_) {
this.detachableBaseWarningBubbleHider_();
this.detachableBaseWarningBubbleHider_ = null;
}
},
/**
* Whether a detachable base warning bubble is being shown for this pod.
* @return {boolean}
*/
showingDetachableBaseWarningBubble: function() {
return this.detachableBaseWarningBubbleHider_ &&
!$('bubble-persistent').hidden &&
$('bubble-persistent').anchor == this.getBubbleAnchor_();
}, },
/** /**
......
...@@ -55,6 +55,8 @@ cr.define('cr.ui', function() { ...@@ -55,6 +55,8 @@ cr.define('cr.ui', function() {
// Whether to hide bubble when key is pressed. // Whether to hide bubble when key is pressed.
hideOnKeyPress_: true, hideOnKeyPress_: true,
persistent_: false,
/** @override */ /** @override */
decorate: function() { decorate: function() {
this.docKeyDownHandler_ = this.handleDocKeyDown_.bind(this); this.docKeyDownHandler_ = this.handleDocKeyDown_.bind(this);
...@@ -92,6 +94,24 @@ cr.define('cr.ui', function() { ...@@ -92,6 +94,24 @@ cr.define('cr.ui', function() {
this.lastBubbleElement_ = value; this.lastBubbleElement_ = value;
}, },
/**
* Whether the bubble should remain shown on user action events (e.g. on
* user clicking, or scrolling outside the bubble). Note that
* {@code this.hideOnKeyPress} has precedence.
* @type {boolean}
*/
set persistent(value) {
this.persistent_ = value
},
/**
* If set, the element at which the bubble is anchored.
* @type {HTMLElement|undefined}
*/
get anchor() {
return this.anchor_;
},
/** /**
* Element that should be focused on tab of last bubble element * Element that should be focused on tab of last bubble element
* to create artificial closed tab-cycle through bubble. * to create artificial closed tab-cycle through bubble.
...@@ -218,9 +238,9 @@ cr.define('cr.ui', function() { ...@@ -218,9 +238,9 @@ cr.define('cr.ui', function() {
* @param {boolean=} opt_oldstyle Optional flag to force old style bubble, * @param {boolean=} opt_oldstyle Optional flag to force old style bubble,
* i.e. pre-MD-style. * i.e. pre-MD-style.
*/ */
showContentForElement: function(el, attachment, opt_content, showContentForElement: function(
opt_offset, opt_padding, opt_match_width, el, attachment, opt_content, opt_offset, opt_padding, opt_match_width,
opt_oldstyle) { opt_oldstyle) {
/** @const */ var ARROW_OFFSET = 25; /** @const */ var ARROW_OFFSET = 25;
/** @const */ var DEFAULT_PADDING = 18; /** @const */ var DEFAULT_PADDING = 18;
...@@ -308,8 +328,8 @@ cr.define('cr.ui', function() { ...@@ -308,8 +328,8 @@ cr.define('cr.ui', function() {
* half of its weight/height. * half of its weight/height.
* @param {number=} opt_padding Optional padding of the bubble. * @param {number=} opt_padding Optional padding of the bubble.
*/ */
showTextForElement: function(el, text, attachment, showTextForElement: function(
opt_offset, opt_padding) { el, text, attachment, opt_offset, opt_padding) {
var span = this.ownerDocument.createElement('span'); var span = this.ownerDocument.createElement('span');
span.textContent = text; span.textContent = text;
this.showContentForElement(el, attachment, span, opt_offset, opt_padding); this.showContentForElement(el, attachment, span, opt_offset, opt_padding);
...@@ -349,7 +369,7 @@ cr.define('cr.ui', function() { ...@@ -349,7 +369,7 @@ cr.define('cr.ui', function() {
* @private * @private
*/ */
handleScroll_: function(e) { handleScroll_: function(e) {
if (!this.hidden) if (!this.hidden && !this.persistent_)
this.hide(); this.hide();
}, },
...@@ -362,7 +382,7 @@ cr.define('cr.ui', function() { ...@@ -362,7 +382,7 @@ cr.define('cr.ui', function() {
if (e.target == this.anchor_) if (e.target == this.anchor_)
return; return;
if (!this.hidden) if (!this.hidden && !this.persistent_)
this.hide(); this.hide();
}, },
...@@ -391,10 +411,9 @@ cr.define('cr.ui', function() { ...@@ -391,10 +411,9 @@ cr.define('cr.ui', function() {
e.preventDefault(); e.preventDefault();
} }
// Close bubble on ESC or on hitting spacebar or Enter at close-button. // Close bubble on ESC or on hitting spacebar or Enter at close-button.
if (e.key == Keys.ESC || if ((e.key == Keys.ESC && !this.persistent_) ||
((e.key == Keys.ENTER || ((e.key == Keys.ENTER || e.key == Keys.SPACE) && e.target &&
e.key == Keys.SPACE) && e.target.classList.contains('close-button')))
e.target && e.target.classList.contains('close-button')))
this.hide(); this.hide();
}, },
...@@ -403,7 +422,7 @@ cr.define('cr.ui', function() { ...@@ -403,7 +422,7 @@ cr.define('cr.ui', function() {
* @private * @private
*/ */
handleWindowBlur_: function(e) { handleWindowBlur_: function(e) {
if (!this.hidden) if (!this.hidden && !this.persistent_)
this.hide(); this.hide();
} }
}; };
......
...@@ -1019,14 +1019,14 @@ cr.define('cr.ui.login', function() { ...@@ -1019,14 +1019,14 @@ cr.define('cr.ui.login', function() {
}; };
/** /**
* Shows sign-in error bubble. * Creates a div element used to display error message in an error bubble.
* @param {number} loginAttempts Number of login attemps tried. *
* @param {string} message Error message to show. * @param {string} message The error message.
* @param {string} link Text to use for help link. * @param {string} link Text to use for help link.
* @param {number} helpId Help topic Id associated with help link. * @param {number} helpId Help topic Id associated with help link.
* @return {!HTMLElement} The error bubble content.
*/ */
DisplayManager.showSignInError = function(loginAttempts, message, link, DisplayManager.createErrorElement_ = function(message, link, helpId) {
helpId) {
var error = document.createElement('div'); var error = document.createElement('div');
var messageDiv = document.createElement('div'); var messageDiv = document.createElement('div');
...@@ -1048,6 +1048,19 @@ cr.define('cr.ui.login', function() { ...@@ -1048,6 +1048,19 @@ cr.define('cr.ui.login', function() {
} }
error.setAttribute('aria-live', 'assertive'); error.setAttribute('aria-live', 'assertive');
return error;
};
/**
* Shows sign-in error bubble.
* @param {number} loginAttempts Number of login attemps tried.
* @param {string} message Error message to show.
* @param {string} link Text to use for help link.
* @param {number} helpId Help topic Id associated with help link.
*/
DisplayManager.showSignInError = function(
loginAttempts, message, link, helpId) {
var error = DisplayManager.createErrorElement_(message, link, helpId);
var currentScreen = Oobe.getInstance().currentScreen; var currentScreen = Oobe.getInstance().currentScreen;
if (currentScreen && typeof currentScreen.showErrorBubble === 'function') { if (currentScreen && typeof currentScreen.showErrorBubble === 'function') {
...@@ -1056,6 +1069,43 @@ cr.define('cr.ui.login', function() { ...@@ -1056,6 +1069,43 @@ cr.define('cr.ui.login', function() {
} }
}; };
/**
* Shows a warning to the user that the detachable base (keyboard) different
* than the one previously used by the user got attached to the device. It
* warn the user that the attached base might be untrusted.
*
* @param {string} username The username of the user with which the error
* bubble is associated. For example, in the account picker screen, it
* identifies the user pod under which the error bubble should be shown.
* @param {string} message Error message to show.
* @param {string} link Text to use for help link.
* @param {number} helpId Help topic Id associated with help link.
*/
DisplayManager.showDetachableBaseChangedWarning = function(
username, message, link, helpId) {
var error = DisplayManager.createErrorElement_(message, link, helpId);
var currentScreen = Oobe.getInstance().currentScreen;
if (currentScreen &&
typeof currentScreen.showDetachableBaseWarningBubble === 'function') {
currentScreen.showDetachableBaseWarningBubble(username, error);
}
};
/**
* Hides the warning bubble shown by {@code showDetachableBaseChangedWarning}.
*
* @param {string} username The username of the user with wich the warning was
* associated.
*/
DisplayManager.hideDetachableBaseChangedWarning = function(username) {
var currentScreen = Oobe.getInstance().currentScreen;
if (currentScreen &&
typeof currentScreen.hideDetachableBaseWarningBubble === 'function') {
currentScreen.hideDetachableBaseWarningBubble(username);
}
};
/** /**
* Shows password changed screen that offers migration. * Shows password changed screen that offers migration.
* @param {boolean} showError Whether to show the incorrect password error. * @param {boolean} showError Whether to show the incorrect password error.
......
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