Commit f3624f6a authored by Pavol Marko's avatar Pavol Marko Committed by Commit Bot

Cert Provisioning: Update UI on state change notifications

Update the Cert Provisioning UI when the state of a certificate
provisioning process changes.
Rate limit updates to the UI to max 1 update per 2 seconds to avoid
flickering / performance issues.

Bug: 1081396
Test: unit_tests
Change-Id: If539d19885e9e2804dfc9d3f662a58dff8f0be53
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2377729Reviewed-by: default avatarDenis Kuznetsov [CET] <antrim@chromium.org>
Reviewed-by: default avatarMichael Ershov <miersh@google.com>
Commit-Queue: Pavol Marko <pmarko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805449}
parent c7557ad5
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h" #include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_common.h" #include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_common.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_scheduler.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_scheduler_user_service.h" #include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_scheduler_user_service.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.h" #include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
...@@ -165,7 +164,12 @@ CertificateProvisioningUiHandler::CertificateProvisioningUiHandler( ...@@ -165,7 +164,12 @@ CertificateProvisioningUiHandler::CertificateProvisioningUiHandler(
: scheduler_for_user_(scheduler_for_user), : scheduler_for_user_(scheduler_for_user),
scheduler_for_device_(ShouldUseDeviceWideProcesses(user_profile) scheduler_for_device_(ShouldUseDeviceWideProcesses(user_profile)
? scheduler_for_device ? scheduler_for_device
: nullptr) {} : nullptr) {
if (scheduler_for_user_)
observed_schedulers_.Add(scheduler_for_user_);
if (scheduler_for_device_)
observed_schedulers_.Add(scheduler_for_device_);
}
CertificateProvisioningUiHandler::~CertificateProvisioningUiHandler() = default; CertificateProvisioningUiHandler::~CertificateProvisioningUiHandler() = default;
...@@ -185,6 +189,34 @@ void CertificateProvisioningUiHandler::RegisterMessages() { ...@@ -185,6 +189,34 @@ void CertificateProvisioningUiHandler::RegisterMessages() {
base::Unretained(this))); base::Unretained(this)));
} }
void CertificateProvisioningUiHandler::OnVisibleStateChanged() {
// If Javascript is not allowed yet, we don't need to cache the update,
// because the UI will request a refresh during its first message to the
// handler.
if (!IsJavascriptAllowed())
return;
if (hold_back_updates_timer_.IsRunning()) {
update_after_hold_back_ = true;
return;
}
constexpr base::TimeDelta kTimeToHoldBackUpdates =
base::TimeDelta::FromMilliseconds(300);
hold_back_updates_timer_.Start(
FROM_HERE, kTimeToHoldBackUpdates,
base::BindOnce(
&CertificateProvisioningUiHandler::OnHoldBackUpdatesTimerExpired,
weak_ptr_factory_.GetWeakPtr()));
RefreshCertificateProvisioningProcesses();
}
unsigned int
CertificateProvisioningUiHandler::ReadAndResetUiRefreshCountForTesting() {
unsigned int value = ui_refresh_count_for_testing_;
ui_refresh_count_for_testing_ = 0;
return value;
}
void CertificateProvisioningUiHandler:: void CertificateProvisioningUiHandler::
HandleRefreshCertificateProvisioningProcesses(const base::ListValue* args) { HandleRefreshCertificateProvisioningProcesses(const base::ListValue* args) {
CHECK_EQ(0U, args->GetSize()); CHECK_EQ(0U, args->GetSize());
...@@ -214,21 +246,6 @@ void CertificateProvisioningUiHandler:: ...@@ -214,21 +246,6 @@ void CertificateProvisioningUiHandler::
return; return;
scheduler->UpdateOneCert(cert_profile_id.GetString()); scheduler->UpdateOneCert(cert_profile_id.GetString());
// Send an update to the UI immediately to reflect a possible status change.
RefreshCertificateProvisioningProcesses();
// Trigger a refresh in a few seconds, in case the state has triggered a
// refresh with the server.
// TODO(https://crbug.com/1045895): Use a real observer instead.
constexpr base::TimeDelta kTimeToWaitBeforeRefresh =
base::TimeDelta::FromSeconds(10);
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CertificateProvisioningUiHandler::
RefreshCertificateProvisioningProcesses,
weak_ptr_factory_.GetWeakPtr()),
kTimeToWaitBeforeRefresh);
} }
void CertificateProvisioningUiHandler:: void CertificateProvisioningUiHandler::
...@@ -244,10 +261,18 @@ void CertificateProvisioningUiHandler:: ...@@ -244,10 +261,18 @@ void CertificateProvisioningUiHandler::
/*is_device_wide=*/true); /*is_device_wide=*/true);
} }
++ui_refresh_count_for_testing_;
FireWebUIListener("certificate-provisioning-processes-changed", FireWebUIListener("certificate-provisioning-processes-changed",
std::move(all_processes)); std::move(all_processes));
} }
void CertificateProvisioningUiHandler::OnHoldBackUpdatesTimerExpired() {
if (update_after_hold_back_) {
update_after_hold_back_ = false;
RefreshCertificateProvisioningProcesses();
}
}
// static // static
bool CertificateProvisioningUiHandler::ShouldUseDeviceWideProcesses( bool CertificateProvisioningUiHandler::ShouldUseDeviceWideProcesses(
Profile* user_profile) { Profile* user_profile) {
......
...@@ -8,7 +8,10 @@ ...@@ -8,7 +8,10 @@
#include <utility> #include <utility>
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "base/timer/timer.h"
#include "base/values.h" #include "base/values.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_scheduler.h"
#include "content/public/browser/web_ui_message_handler.h" #include "content/public/browser/web_ui_message_handler.h"
class Profile; class Profile;
...@@ -16,9 +19,9 @@ class Profile; ...@@ -16,9 +19,9 @@ class Profile;
namespace chromeos { namespace chromeos {
namespace cert_provisioning { namespace cert_provisioning {
class CertProvisioningScheduler; class CertificateProvisioningUiHandler
: public content::WebUIMessageHandler,
class CertificateProvisioningUiHandler : public content::WebUIMessageHandler { public CertProvisioningSchedulerObserver {
public: public:
// Creates a CertificateProvisioningUiHandler for |user_profile|, which uses: // Creates a CertificateProvisioningUiHandler for |user_profile|, which uses:
// (*) The CertProvisioningScheduler associated with |user_profile|, if any. // (*) The CertProvisioningScheduler associated with |user_profile|, if any.
...@@ -51,6 +54,13 @@ class CertificateProvisioningUiHandler : public content::WebUIMessageHandler { ...@@ -51,6 +54,13 @@ class CertificateProvisioningUiHandler : public content::WebUIMessageHandler {
// content::WebUIMessageHandler. // content::WebUIMessageHandler.
void RegisterMessages() override; void RegisterMessages() override;
// CertProvisioningSchedulerObserver:
void OnVisibleStateChanged() override;
// For testing: Reads the count of UI refreshes sent to the WebUI (since
// instantiation or the last call to this function) and resets it to 0.
unsigned int ReadAndResetUiRefreshCountForTesting();
private: private:
// Send the list of certificate provisioning processes to the UI, triggered by // Send the list of certificate provisioning processes to the UI, triggered by
// the UI when it loads. // the UI when it loads.
...@@ -70,6 +80,9 @@ class CertificateProvisioningUiHandler : public content::WebUIMessageHandler { ...@@ -70,6 +80,9 @@ class CertificateProvisioningUiHandler : public content::WebUIMessageHandler {
// Send the list of certificate provisioning processes to the UI. // Send the list of certificate provisioning processes to the UI.
void RefreshCertificateProvisioningProcesses(); void RefreshCertificateProvisioningProcesses();
// Called when the |hold_back_updates_timer_| expires.
void OnHoldBackUpdatesTimerExpired();
// Returns true if device-wide certificate provisioning processes should be // Returns true if device-wide certificate provisioning processes should be
// displayed, i.e. if the |user_profile| is affiliated. // displayed, i.e. if the |user_profile| is affiliated.
static bool ShouldUseDeviceWideProcesses(Profile* user_profile); static bool ShouldUseDeviceWideProcesses(Profile* user_profile);
...@@ -82,6 +95,23 @@ class CertificateProvisioningUiHandler : public content::WebUIMessageHandler { ...@@ -82,6 +95,23 @@ class CertificateProvisioningUiHandler : public content::WebUIMessageHandler {
// Unowned. // Unowned.
CertProvisioningScheduler* const scheduler_for_device_; CertProvisioningScheduler* const scheduler_for_device_;
// When this timer is running, updates provided by the schedulers should not
// be forwarded to the UI until it fires. Used to prevent spamming the UI if
// many events come in in rapid succession.
base::OneShotTimer hold_back_updates_timer_;
// When this is true, an update should be sent to the UI when
// |hold_back_updates_timer_| fires.
bool update_after_hold_back_ = false;
// Keeps track of the count of UI refreshes sent to the WebUI.
unsigned int ui_refresh_count_for_testing_ = 0;
// Keeps track of the CertProvisioningSchedulers that this UI handler
// observes.
ScopedObserver<CertProvisioningScheduler, CertProvisioningSchedulerObserver>
observed_schedulers_{this};
base::WeakPtrFactory<CertificateProvisioningUiHandler> weak_ptr_factory_{ base::WeakPtrFactory<CertificateProvisioningUiHandler> weak_ptr_factory_{
this}; this};
}; };
......
...@@ -36,6 +36,8 @@ namespace { ...@@ -36,6 +36,8 @@ namespace {
using ::testing::Return; using ::testing::Return;
using ::testing::ReturnPointee; using ::testing::ReturnPointee;
using ::testing::ReturnRef; using ::testing::ReturnRef;
using ::testing::SaveArg;
using ::testing::StrictMock;
using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAre;
// Extracted from a X.509 certificate using the command: // Extracted from a X.509 certificate using the command:
...@@ -137,20 +139,28 @@ class CertificateProvisioningUiHandlerTestBase : public ::testing::Test { ...@@ -137,20 +139,28 @@ class CertificateProvisioningUiHandlerTestBase : public ::testing::Test {
profile_helper_for_testing_.GetProfile())); profile_helper_for_testing_.GetProfile()));
web_ui_.set_web_contents(web_contents_.get()); web_ui_.set_web_contents(web_contents_.get());
auto handler = std::make_unique<CertificateProvisioningUiHandler>(
GetProfile(), &scheduler_for_user_, &scheduler_for_device_);
handler_ = handler.get();
web_ui_.AddMessageHandler(std::move(handler));
EXPECT_CALL(scheduler_for_user_, GetWorkers) EXPECT_CALL(scheduler_for_user_, GetWorkers)
.WillRepeatedly(ReturnRef(user_workers_)); .WillRepeatedly(ReturnRef(user_workers_));
EXPECT_CALL(scheduler_for_user_, GetFailedCertProfileIds) EXPECT_CALL(scheduler_for_user_, GetFailedCertProfileIds)
.WillRepeatedly(ReturnRef(user_failed_workers_)); .WillRepeatedly(ReturnRef(user_failed_workers_));
EXPECT_CALL(scheduler_for_user_, AddObserver(_))
.WillOnce(SaveArg<0>(&scheduler_observer_for_user_));
EXPECT_CALL(scheduler_for_user_, RemoveObserver(_)).Times(1);
if (user_is_affiliated) {
EXPECT_CALL(scheduler_for_device_, GetWorkers)
.WillRepeatedly(ReturnRef(device_workers_));
EXPECT_CALL(scheduler_for_device_, GetFailedCertProfileIds)
.WillRepeatedly(ReturnRef(device_failed_workers_));
EXPECT_CALL(scheduler_for_device_, AddObserver(_))
.WillOnce(SaveArg<0>(&scheduler_observer_for_device_));
EXPECT_CALL(scheduler_for_device_, RemoveObserver(_)).Times(1);
}
EXPECT_CALL(scheduler_for_device_, GetWorkers) auto handler = std::make_unique<CertificateProvisioningUiHandler>(
.WillRepeatedly(ReturnRef(device_workers_)); GetProfile(), &scheduler_for_user_, &scheduler_for_device_);
EXPECT_CALL(scheduler_for_device_, GetFailedCertProfileIds) handler_ = handler.get();
.WillRepeatedly(ReturnRef(device_failed_workers_)); web_ui_.AddMessageHandler(std::move(handler));
} }
~CertificateProvisioningUiHandlerTestBase() override {} ~CertificateProvisioningUiHandlerTestBase() override {}
...@@ -166,20 +176,14 @@ class CertificateProvisioningUiHandlerTestBase : public ::testing::Test { ...@@ -166,20 +176,14 @@ class CertificateProvisioningUiHandlerTestBase : public ::testing::Test {
crypto::EnsureNSSInit(); crypto::EnsureNSSInit();
} }
void RefreshCertProvisioningProcesses( // Use in ASSERT_NO_FATAL_FAILURE.
void ExtractCertProvisioningProcesses(
std::vector<base::Value>& args,
base::Value* out_all_processes, base::Value* out_all_processes,
std::vector<std::string>* out_profile_ids) { std::vector<std::string>* out_profile_ids) {
content::TestWebUIListenerObserver result_waiter( ASSERT_EQ(1U, args.size());
&web_ui_, "certificate-provisioning-processes-changed"); ASSERT_TRUE(args[0].is_list());
*out_all_processes = std::move(args[0]);
base::ListValue args;
web_ui_.HandleReceivedMessage("refreshCertificateProvisioningProcessses",
&args);
result_waiter.Wait();
ASSERT_EQ(1U, result_waiter.args().size());
ASSERT_TRUE(result_waiter.args()[0].is_list());
*out_all_processes = std::move(result_waiter.args()[0]);
// Extract all profile ids for easier verification. // Extract all profile ids for easier verification.
if (!out_profile_ids) if (!out_profile_ids)
...@@ -192,21 +196,40 @@ class CertificateProvisioningUiHandlerTestBase : public ::testing::Test { ...@@ -192,21 +196,40 @@ class CertificateProvisioningUiHandlerTestBase : public ::testing::Test {
} }
} }
// Use in ASSERT_NO_FATAL_FAILURE.
void RefreshCertProvisioningProcesses(
base::Value* out_all_processes,
std::vector<std::string>* out_profile_ids) {
content::TestWebUIListenerObserver result_waiter(
&web_ui_, "certificate-provisioning-processes-changed");
base::ListValue args;
web_ui_.HandleReceivedMessage("refreshCertificateProvisioningProcessses",
&args);
result_waiter.Wait();
ASSERT_NO_FATAL_FAILURE(ExtractCertProvisioningProcesses(
result_waiter.args(), out_all_processes, out_profile_ids));
}
protected: protected:
Profile* GetProfile() { return profile_helper_for_testing_.GetProfile(); } Profile* GetProfile() { return profile_helper_for_testing_.GetProfile(); }
std::string der_encoded_spki_; std::string der_encoded_spki_;
content::BrowserTaskEnvironment task_environment_; content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
ProfileHelperForTesting profile_helper_for_testing_; ProfileHelperForTesting profile_helper_for_testing_;
WorkerMap user_workers_; WorkerMap user_workers_;
base::flat_map<CertProfileId, FailedWorkerInfo> user_failed_workers_; base::flat_map<CertProfileId, FailedWorkerInfo> user_failed_workers_;
MockCertProvisioningScheduler scheduler_for_user_; StrictMock<MockCertProvisioningScheduler> scheduler_for_user_;
CertProvisioningSchedulerObserver* scheduler_observer_for_user_ = nullptr;
WorkerMap device_workers_; WorkerMap device_workers_;
base::flat_map<CertProfileId, FailedWorkerInfo> device_failed_workers_; base::flat_map<CertProfileId, FailedWorkerInfo> device_failed_workers_;
MockCertProvisioningScheduler scheduler_for_device_; StrictMock<MockCertProvisioningScheduler> scheduler_for_device_;
CertProvisioningSchedulerObserver* scheduler_observer_for_device_ = nullptr;
content::TestWebUI web_ui_; content::TestWebUI web_ui_;
std::unique_ptr<content::WebContents> web_contents_; std::unique_ptr<content::WebContents> web_contents_;
...@@ -266,14 +289,14 @@ TEST_F(CertificateProvisioningUiHandlerTest, HasProcesses) { ...@@ -266,14 +289,14 @@ TEST_F(CertificateProvisioningUiHandlerTest, HasProcesses) {
R"({ R"({
"certProfileId": "user_cert_profile_1", "certProfileId": "user_cert_profile_1",
"isDeviceWide": false, "isDeviceWide": false,
"publicKey": "$1", "publicKey": "$0",
"stateId": 1, "stateId": 1,
"status": "$0", "status": "$1",
"timeSinceLastUpdate": "" "timeSinceLastUpdate": ""
})", })",
{l10n_util::GetStringUTF8( {kFormattedPublicKey,
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR_WAITING), l10n_util::GetStringUTF8(
kFormattedPublicKey})); IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR_WAITING)}));
} }
TEST_F(CertificateProvisioningUiHandlerAffiliatedTest, HasProcessesAffiliated) { TEST_F(CertificateProvisioningUiHandlerAffiliatedTest, HasProcessesAffiliated) {
...@@ -304,28 +327,93 @@ TEST_F(CertificateProvisioningUiHandlerAffiliatedTest, HasProcessesAffiliated) { ...@@ -304,28 +327,93 @@ TEST_F(CertificateProvisioningUiHandlerAffiliatedTest, HasProcessesAffiliated) {
R"({ R"({
"certProfileId": "user_cert_profile_1", "certProfileId": "user_cert_profile_1",
"isDeviceWide": false, "isDeviceWide": false,
"publicKey": "$1", "publicKey": "$0",
"stateId": 1, "stateId": 1,
"status": "$0", "status": "$1",
"timeSinceLastUpdate": "" "timeSinceLastUpdate": ""
})", })",
{l10n_util::GetStringUTF8( {kFormattedPublicKey,
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR_WAITING), l10n_util::GetStringUTF8(
kFormattedPublicKey})); IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR_WAITING)}));
EXPECT_EQ( EXPECT_EQ(
GetByProfileId(all_processes, "device_cert_profile_1"), GetByProfileId(all_processes, "device_cert_profile_1"),
FormatJsonDict( FormatJsonDict(
R"({ R"({
"certProfileId": "device_cert_profile_1", "certProfileId": "device_cert_profile_1",
"isDeviceWide": true, "isDeviceWide": true,
"publicKey": "$1", "publicKey": "$0",
"stateId": 10, "stateId": 10,
"status": "$0", "status": "$1",
"timeSinceLastUpdate": ""
})",
{kFormattedPublicKey,
l10n_util::GetStringUTF8(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_FAILURE)}));
}
TEST_F(CertificateProvisioningUiHandlerTest, Updates) {
base::Value all_processes;
std::vector<std::string> profile_ids;
// Perform an initial JS-side initiated refresh so that javascript is
// considered allowed by the UI handler.
ASSERT_NO_FATAL_FAILURE(
RefreshCertProvisioningProcesses(&all_processes, &profile_ids));
ASSERT_THAT(profile_ids, UnorderedElementsAre());
EXPECT_EQ(1U, handler_->ReadAndResetUiRefreshCountForTesting());
auto user_cert_worker = std::make_unique<MockCertProvisioningWorker>();
SetupMockCertProvisioningWorker(
user_cert_worker.get(), CertProvisioningWorkerState::kKeypairGenerated,
&der_encoded_spki_);
user_workers_["user_cert_profile_1"] = std::move(user_cert_worker);
// The user worker triggers an update
content::TestWebUIListenerObserver result_waiter_1(
&web_ui_, "certificate-provisioning-processes-changed");
scheduler_observer_for_user_->OnVisibleStateChanged();
EXPECT_EQ(1U, handler_->ReadAndResetUiRefreshCountForTesting());
result_waiter_1.Wait();
ASSERT_NO_FATAL_FAILURE(ExtractCertProvisioningProcesses(
result_waiter_1.args(), &all_processes, &profile_ids));
// Only the user worker is expected to be displayed in the UI, because the
// user is not affiliated.
ASSERT_THAT(profile_ids, UnorderedElementsAre("user_cert_profile_1"));
EXPECT_EQ(
GetByProfileId(all_processes, "user_cert_profile_1"),
FormatJsonDict(
R"({
"certProfileId": "user_cert_profile_1",
"isDeviceWide": false,
"publicKey": "$0",
"stateId": 1,
"status": "$1",
"timeSinceLastUpdate": "" "timeSinceLastUpdate": ""
})", })",
{l10n_util::GetStringUTF8( {kFormattedPublicKey,
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_FAILURE), l10n_util::GetStringUTF8(
kFormattedPublicKey})); IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR_WAITING)}));
content::TestWebUIListenerObserver result_waiter_2(
&web_ui_, "certificate-provisioning-processes-changed");
scheduler_observer_for_user_->OnVisibleStateChanged();
// Another update does not trigger a UI update for the holdoff time.
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(299));
EXPECT_EQ(0U, handler_->ReadAndResetUiRefreshCountForTesting());
// When the holdoff time has elapsed, an UI update is triggered.
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(2));
EXPECT_EQ(1U, handler_->ReadAndResetUiRefreshCountForTesting());
result_waiter_2.Wait();
base::Value all_processes_2;
ASSERT_NO_FATAL_FAILURE(ExtractCertProvisioningProcesses(
result_waiter_2.args(), &all_processes_2, /*profile_ids=*/nullptr));
EXPECT_EQ(all_processes, all_processes_2);
} }
} // namespace } // namespace
......
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