Commit 12482bcb authored by Fabian Sommer's avatar Fabian Sommer Committed by Commit Bot

Add Notifications for SecurityTokenSession

Add notification UI to SecurityTokenSessionController. These
notifications appear the first time a user was logged out / their
session was locked because of the SecurityTokenSessionBehavior policy.

A profile pref is used to save whether a user has already seen such a
notification once. If the session is locked, the controller creates
the notification directly. If the user is logged out, a local state pref
is used to schedule the creation of the notification.

Bug: 1131450
Change-Id: I27da14ec8d7a2fed53c46764ef64d156f399e86d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2505877Reviewed-by: default avatarDominic Battré <battre@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Reviewed-by: default avatarDenis Kuznetsov [CET] <antrim@chromium.org>
Commit-Queue: Fabian Sommer <fabiansommer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827777}
parent 10b21b4a
......@@ -5958,5 +5958,14 @@ other {Your <ph name="DEVICE_TYPE">{1}<ex>Chromebook</ex></ph> will be locked au
Your <ph name="DEVICE_TYPE">{0}<ex>Chromebook</ex></ph> will be locked now.
<ph name="DOMAIN">{1}<ex>example.com</ex></ph> requires you to keep your smart card inserted.
</message>
<message name="IDS_SECURITY_TOKEN_SESSION_LOGOUT_MESSAGE_TITLE" desc="This is the title of a notification appearing the first time a user is logged out because they removed their smart card. This only applies to users that authenticate with a smart card and for who this behavior was enabled by a policy.">
You were logged out.
</message>
<message name="IDS_SECURITY_TOKEN_SESSION_LOCK_MESSAGE_TITLE" desc="This is the title of a notification appearing the first time the session of a user is locked because they removed their smart card. This only applies to users that authenticate with a smart card and for who this behavior was enabled by a policy.">
Your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph> was locked.
</message>
<message name="IDS_SECURITY_TOKEN_SESSION_LOGOUT_MESSAGE_BODY" desc="This is the text of a notification appearing the first time the a user was logged out because they removed their smart card. This only applies to users that authenticate with a smart card and for who this behavior was enabled by a policy.">
<ph name="DOMAIN">$1<ex>example.com</ex></ph> requires you to keep your smart card inserted.
</message>
</grit-part>
a1f9461d5574d2eff5164edb133c57052fc755f5
\ No newline at end of file
c9c4e8db94b73792a129ff57b8f1f846447e04be
\ No newline at end of file
c9c4e8db94b73792a129ff57b8f1f846447e04be
\ No newline at end of file
......@@ -4,11 +4,28 @@
#include "chrome/browser/chromeos/login/security_token_session_controller.h"
#include <string>
#include "ash/public/cpp/notification_utils.h"
#include "base/bind.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.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/user.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/message_center/public/cpp/notification.h"
#include "url/gurl.h"
#include "url/url_constants.h"
namespace chromeos {
namespace login {
......@@ -22,6 +39,11 @@ constexpr char kIgnorePrefValue[] = "IGNORE";
constexpr char kLogoutPrefValue[] = "LOGOUT";
constexpr char kLockPrefValue[] = "LOCK";
constexpr char kNotifierSecurityTokenSession[] =
"ash.security_token_session_controller";
constexpr char kNotificationId[] =
"security_token_session_controller_notification";
SecurityTokenSessionController::Behavior ParseBehaviorPrefValue(
const std::string& behavior) {
if (behavior == kIgnorePrefValue)
......@@ -34,14 +56,67 @@ SecurityTokenSessionController::Behavior ParseBehaviorPrefValue(
return SecurityTokenSessionController::Behavior::kIgnore;
}
std::string GetEnterpriseDomainFromEmail(const std::string& email) {
size_t email_separator_pos = email.find('@');
bool is_email = email_separator_pos != std::string::npos &&
email_separator_pos < email.length() - 1;
if (!is_email)
return std::string();
return gaia::ExtractDomainName(email);
}
// Checks if `domain` represents a valid domain. Returns false if `domain` is
// malformed. Returns the host part, which should be displayed to the user, in
// `sanitized_domain`.
bool SanitizeDomain(const std::string& domain, std::string& sanitized_domain) {
// Add "http://" to the url. Otherwise, "example.com" would be rejected,
// even though it has the format that is expected for `domain`.
GURL url(std::string(url::kHttpScheme) +
std::string(url::kStandardSchemeSeparator) + domain);
if (!url.is_valid())
return false;
if (!url.has_host())
return false;
sanitized_domain = url.host();
return true;
}
void DisplayNotification(const base::string16& title,
const base::string16& text) {
std::unique_ptr<message_center::Notification> notification =
ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId, title,
text,
/*display_source=*/base::string16(), /*origin_url=*/GURL(),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT,
kNotifierSecurityTokenSession),
/*optional_fields=*/{},
new message_center::HandleNotificationClickDelegate(
base::DoNothing::Repeatedly()),
chromeos::kEnterpriseIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
notification->set_fullscreen_visibility(
message_center::FullscreenVisibility::OVER_USER);
notification->SetSystemPriority();
SystemNotificationHelper::GetInstance()->Display(*notification);
}
} // namespace
SecurityTokenSessionController::SecurityTokenSessionController(
PrefService* pref_service)
: pref_service_(pref_service) {
PrefService* local_state,
PrefService* profile_prefs,
const user_manager::User* user)
: local_state_(local_state), profile_prefs_(profile_prefs), user_(user) {
DCHECK(local_state_);
DCHECK(profile_prefs_);
DCHECK(user_);
UpdateNotificationPref();
UpdateBehaviorPref();
pref_change_registrar_.Init(pref_service_);
pref_change_registrar_.Init(profile_prefs_);
base::RepeatingClosure behavior_pref_changed_callback =
base::BindRepeating(&SecurityTokenSessionController::UpdateBehaviorPref,
base::Unretained(this));
......@@ -62,12 +137,50 @@ void SecurityTokenSessionController::Shutdown() {
}
// static
void SecurityTokenSessionController::RegisterPrefs(
void SecurityTokenSessionController::RegisterLocalStatePrefs(
PrefRegistrySimple* registry) {
registry->RegisterStringPref(
prefs::kSecurityTokenSessionNotificationScheduledDomain, "");
}
// static
void SecurityTokenSessionController::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterStringPref(prefs::kSecurityTokenSessionBehavior,
kIgnorePrefValue);
registry->RegisterIntegerPref(prefs::kSecurityTokenSessionNotificationSeconds,
0);
registry->RegisterBooleanPref(
prefs::kSecurityTokenSessionNotificationDisplayed, false);
}
// static
void SecurityTokenSessionController::MaybeDisplayLoginScreenNotification() {
PrefService* local_state = g_browser_process->local_state();
const PrefService::Preference* scheduled_notification_domain =
local_state->FindPreference(
prefs::kSecurityTokenSessionNotificationScheduledDomain);
if (!scheduled_notification_domain ||
scheduled_notification_domain->IsDefaultValue() ||
!scheduled_notification_domain->GetValue()->is_string()) {
// 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 sanitized_domain;
if (!SanitizeDomain(scheduled_notification_domain->GetValue()->GetString(),
sanitized_domain)) {
// The pref value is invalid.
return;
}
DisplayNotification(
l10n_util::GetStringUTF16(
IDS_SECURITY_TOKEN_SESSION_LOGOUT_MESSAGE_TITLE),
l10n_util::GetStringFUTF16(IDS_SECURITY_TOKEN_SESSION_LOGOUT_MESSAGE_BODY,
base::UTF8ToUTF16(sanitized_domain)));
}
void SecurityTokenSessionController::UpdateBehaviorPref() {
......@@ -76,14 +189,49 @@ void SecurityTokenSessionController::UpdateBehaviorPref() {
void SecurityTokenSessionController::UpdateNotificationPref() {
notification_seconds_ =
base::TimeDelta::FromSeconds(pref_service_->GetInteger(
base::TimeDelta::FromSeconds(profile_prefs_->GetInteger(
prefs::kSecurityTokenSessionNotificationSeconds));
}
SecurityTokenSessionController::Behavior
SecurityTokenSessionController::GetBehaviorFromPref() const {
return ParseBehaviorPrefValue(
pref_service_->GetString(prefs::kSecurityTokenSessionBehavior));
profile_prefs_->GetString(prefs::kSecurityTokenSessionBehavior));
}
void SecurityTokenSessionController::AddLockNotification() const {
// A user should see the notification only the first time their session is
// locked.
if (profile_prefs_->GetBoolean(
prefs::kSecurityTokenSessionNotificationDisplayed)) {
return;
}
profile_prefs_->SetBoolean(prefs::kSecurityTokenSessionNotificationDisplayed,
true);
std::string domain = GetEnterpriseDomainFromEmail(user_->GetDisplayEmail());
DisplayNotification(
l10n_util::GetStringFUTF16(IDS_SECURITY_TOKEN_SESSION_LOCK_MESSAGE_TITLE,
ui::GetChromeOSDeviceName()),
l10n_util::GetStringFUTF16(IDS_SECURITY_TOKEN_SESSION_LOGOUT_MESSAGE_BODY,
base::UTF8ToUTF16(domain)));
}
void SecurityTokenSessionController::ScheduleLogoutNotification() const {
// The notification can not be created directly, since it will not persist
// after the session is ended. Instead, use local state to schedule the
// creation of a notification.
if (profile_prefs_->GetBoolean(
prefs::kSecurityTokenSessionNotificationDisplayed)) {
// A user should see the notification only the first time they are logged
// out.
return;
}
profile_prefs_->SetBoolean(prefs::kSecurityTokenSessionNotificationDisplayed,
true);
local_state_->SetString(
prefs::kSecurityTokenSessionNotificationScheduledDomain,
GetEnterpriseDomainFromEmail(user_->GetDisplayEmail()));
}
} // namespace login
......
......@@ -9,6 +9,7 @@
#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"
namespace chromeos {
namespace login {
......@@ -24,7 +25,9 @@ class SecurityTokenSessionController : public KeyedService {
public:
enum class Behavior { kIgnore, kLogout, kLock };
explicit SecurityTokenSessionController(PrefService* pref_service);
SecurityTokenSessionController(PrefService* local_state,
PrefService* profile_prefs,
const user_manager::User* user);
SecurityTokenSessionController(const SecurityTokenSessionController& other) =
delete;
SecurityTokenSessionController& operator=(
......@@ -34,15 +37,25 @@ class SecurityTokenSessionController : public KeyedService {
// KeyedService
void Shutdown() override;
static void RegisterPrefs(PrefRegistrySimple* registry);
static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
// If this controller logged the user out just before, display a notification
// explaining why this happened. This is only done the first time this
// happens for a user on a device.
static void MaybeDisplayLoginScreenNotification();
private:
Behavior GetBehaviorFromPref() const;
void UpdateBehaviorPref();
void UpdateNotificationPref();
PrefService* const pref_service_;
void AddLockNotification() const;
void ScheduleLogoutNotification() const;
PrefService* const local_state_;
PrefService* const profile_prefs_;
const user_manager::User* const user_;
PrefChangeRegistrar pref_change_registrar_;
Behavior behavior_ = Behavior::kIgnore;
base::TimeDelta notification_seconds_;
......
......@@ -4,6 +4,7 @@
#include "chrome/browser/chromeos/login/security_token_session_controller_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/chromeos/login/security_token_session_controller.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
......@@ -46,7 +47,15 @@ KeyedService* SecurityTokenSessionControllerFactory::BuildServiceInstanceFor(
if (!chromeos::ProfileHelper::IsPrimaryProfile(profile))
return nullptr;
return new SecurityTokenSessionController(profile->GetPrefs());
PrefService* local_state = g_browser_process->local_state();
if (!local_state) {
// This can happen in tests that do not have local state.
return nullptr;
}
user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(
Profile::FromBrowserContext(context));
return new SecurityTokenSessionController(local_state, profile->GetPrefs(),
user);
}
content::BrowserContext*
......
......@@ -23,6 +23,7 @@
#include "chrome/browser/chromeos/login/reauth_stats.h"
#include "chrome/browser/chromeos/login/screens/chrome_user_selection_screen.h"
#include "chrome/browser/chromeos/login/screens/gaia_screen.h"
#include "chrome/browser/chromeos/login/security_token_session_controller.h"
#include "chrome/browser/chromeos/login/ui/login_display.h"
#include "chrome/browser/chromeos/login/ui/login_display_mojo.h"
#include "chrome/browser/chromeos/login/user_board_view_mojo.h"
......@@ -284,6 +285,8 @@ void LoginDisplayHostMojo::OnStartSignInScreen() {
UpdateAddUserButtonStatus();
OnStartSignInScreenCommon();
login::SecurityTokenSessionController::MaybeDisplayLoginScreenNotification();
}
void LoginDisplayHostMojo::OnPreferencesChanged() {
......
......@@ -687,6 +687,8 @@ void RegisterLocalState(PrefRegistrySimple* registry) {
chromeos::language_prefs::RegisterPrefs(registry);
chromeos::local_search_service::SearchMetricsReporterSync::
RegisterLocalStatePrefs(registry);
chromeos::login::SecurityTokenSessionController::RegisterLocalStatePrefs(
registry);
chromeos::MultiProfileUserController::RegisterPrefs(registry);
chromeos::NetworkMetadataStore::RegisterPrefs(registry);
chromeos::NetworkThrottlingObserver::RegisterPrefs(registry);
......@@ -956,7 +958,8 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
chromeos::file_system_provider::RegisterProfilePrefs(registry);
chromeos::full_restore::RegisterProfilePrefs(registry);
chromeos::KerberosCredentialsManager::RegisterProfilePrefs(registry);
chromeos::login::SecurityTokenSessionController::RegisterPrefs(registry);
chromeos::login::SecurityTokenSessionController::RegisterProfilePrefs(
registry);
chromeos::multidevice_setup::MultiDeviceSetupService::RegisterProfilePrefs(
registry);
chromeos::MultiProfileUserController::RegisterProfilePrefs(registry);
......
......@@ -3104,6 +3104,17 @@ const char kSecurityTokenSessionBehavior[] = "security_token_session_behavior";
// this pref is set to 0, the action happens immediately.
const char kSecurityTokenSessionNotificationSeconds[] =
"security_token_session_notification_seconds";
// In addition to the notification described directly above, another
// notification will be displayed after the action happened. This only happens
// once for a user. This boolean pref saves whether this notification was
// already displayed for a user.
const char kSecurityTokenSessionNotificationDisplayed[] =
"security_token_session_notification_displayed";
// This string pref is set when the notification after the action mentioned
// above is about to be displayed. It contains the domain that manages the user
// who was logged out, to be used as part of the notification message.
const char kSecurityTokenSessionNotificationScheduledDomain[] =
"security_token_session_notification_scheduled";
#endif // defined(OS_CHROMEOS)
} // namespace prefs
......@@ -1087,6 +1087,8 @@ extern const char kLacrosAllowed[];
#if defined(OS_CHROMEOS)
extern const char kSecurityTokenSessionBehavior[];
extern const char kSecurityTokenSessionNotificationSeconds[];
extern const char kSecurityTokenSessionNotificationDisplayed[];
extern const char kSecurityTokenSessionNotificationScheduledDomain[];
#endif
} // namespace prefs
......
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