blob: 7817c30c431fd8ff2e5cfd624f785e1ce9b0d73b [file] [log] [blame]
// Copyright 2020 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 <string>
#include "chrome/browser/ui/webui/certificate_provisioning_ui_handler.h"
#include "base/bind.h"
#include "base/containers/span.h"
#include "base/strings/string16.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.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_scheduler_user_service.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/common/net/x509_certificate_model_nss.h"
#include "chrome/grit/generated_resources.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/web_ui.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/time_format.h"
namespace chromeos {
namespace cert_provisioning {
namespace {
// Returns the per-user CertProvisioningScheduler for |user_profile|, if it has
// any.
CertProvisioningScheduler* GetCertProvisioningSchedulerForUser(
Profile* user_profile) {
CertProvisioningSchedulerUserService* user_service =
CertProvisioningSchedulerUserServiceFactory::GetForProfile(user_profile);
if (!user_service)
return nullptr;
return user_service->scheduler();
}
// Returns the per-device CertProvisioningScheduler, if it exists. No
// affiliation check is done here.
CertProvisioningScheduler* GetCertProvisioningSchedulerForDevice() {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
return connector->GetDeviceCertProvisioningScheduler();
}
// Returns localized representation for the state of a certificate provisioning
// process.
base::string16 GetProvisioningProcessStatus(CertProvisioningWorkerState state) {
using CertProvisioningWorkerState = CertProvisioningWorkerState;
switch (state) {
case CertProvisioningWorkerState ::kInitState:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR);
case CertProvisioningWorkerState ::kKeypairGenerated:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR_WAITING);
case CertProvisioningWorkerState::kStartCsrResponseReceived:
// Intentional fall-through.
case CertProvisioningWorkerState::kVaChallengeFinished:
// Intentional fall-through.
case CertProvisioningWorkerState::kKeyRegistered:
// Intentional fall-through.
case CertProvisioningWorkerState::kKeypairMarked:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR);
case CertProvisioningWorkerState::kSignCsrFinished:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR_WAITING);
case CertProvisioningWorkerState::kFinishCsrResponseReceived:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_WAITING_FOR_CA);
case CertProvisioningWorkerState::kSucceeded:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_SUCCESS);
case CertProvisioningWorkerState::kFailed:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_FAILURE);
case CertProvisioningWorkerState::kInconsistentDataError:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_PREPARING_CSR_WAITING);
case CertProvisioningWorkerState::kCanceled:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CERTIFICATE_MANAGER_PROVISIONING_STATUS_CANCELED);
}
NOTREACHED();
}
// Returns a localized representation of the last update time as a delay (e.g.
// "5 minutes ago".
base::string16 GetTimeSinceLastUpdate(base::Time last_update_time) {
const base::Time now = base::Time::NowFromSystemTime();
if (last_update_time.is_null() || last_update_time > now)
return base::string16();
const base::TimeDelta elapsed_time = now - last_update_time;
return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_ELAPSED,
ui::TimeFormat::LENGTH_SHORT, elapsed_time);
}
base::Value CreateProvisioningProcessEntry(
const std::string& cert_profile_id,
const std::string& cert_profile_name,
bool is_device_wide,
CertProvisioningWorkerState state,
base::Time time_since_last_update,
const std::string& public_key_spki_der) {
base::Value entry(base::Value::Type::DICTIONARY);
entry.SetStringKey("certProfileId", cert_profile_id);
entry.SetStringKey("certProfileName", cert_profile_name);
entry.SetBoolKey("isDeviceWide", is_device_wide);
entry.SetStringKey("status", GetProvisioningProcessStatus(state));
entry.SetIntKey("stateId", static_cast<int>(state));
entry.SetStringKey("timeSinceLastUpdate",
GetTimeSinceLastUpdate(time_since_last_update));
auto spki_der_bytes = base::as_bytes(base::make_span(public_key_spki_der));
entry.SetStringKey(
"publicKey",
x509_certificate_model::ProcessRawSubjectPublicKeyInfo(spki_der_bytes));
return entry;
}
// Collects information about certificate provisioning processes from
// |cert_provisioning_scheduler| and appends them to |list_to_append_to|.
void CollectProvisioningProcesses(
base::Value* list_to_append_to,
CertProvisioningScheduler* cert_provisioning_scheduler,
bool is_device_wide) {
for (const auto& worker_entry : cert_provisioning_scheduler->GetWorkers()) {
CertProvisioningWorker* worker = worker_entry.second.get();
list_to_append_to->Append(CreateProvisioningProcessEntry(
worker_entry.first, worker->GetCertProfile().name, is_device_wide,
worker->GetState(), worker->GetLastUpdateTime(),
worker->GetPublicKey()));
}
for (const auto& failed_worker_entry :
cert_provisioning_scheduler->GetFailedCertProfileIds()) {
const FailedWorkerInfo& worker = failed_worker_entry.second;
list_to_append_to->Append(CreateProvisioningProcessEntry(
failed_worker_entry.first, worker.cert_profile_name, is_device_wide,
CertProvisioningWorkerState::kFailed, worker.last_update_time,
worker.public_key));
}
}
} // namespace
// static
std::unique_ptr<CertificateProvisioningUiHandler>
CertificateProvisioningUiHandler::CreateForProfile(Profile* user_profile) {
return std::make_unique<CertificateProvisioningUiHandler>(
user_profile, GetCertProvisioningSchedulerForUser(user_profile),
GetCertProvisioningSchedulerForDevice());
}
CertificateProvisioningUiHandler::CertificateProvisioningUiHandler(
Profile* user_profile,
CertProvisioningScheduler* scheduler_for_user,
CertProvisioningScheduler* scheduler_for_device)
: scheduler_for_user_(scheduler_for_user),
scheduler_for_device_(ShouldUseDeviceWideProcesses(user_profile)
? scheduler_for_device
: nullptr) {
if (scheduler_for_user_)
observed_schedulers_.AddObservation(scheduler_for_user_);
if (scheduler_for_device_)
observed_schedulers_.AddObservation(scheduler_for_device_);
}
CertificateProvisioningUiHandler::~CertificateProvisioningUiHandler() = default;
void CertificateProvisioningUiHandler::RegisterMessages() {
// Passing base::Unretained(this) to web_ui()->RegisterMessageCallback is fine
// because in chrome Web UI, web_ui() has acquired ownership of |this| and
// maintains the life time of |this| accordingly.
web_ui()->RegisterMessageCallback(
"refreshCertificateProvisioningProcessses",
base::BindRepeating(&CertificateProvisioningUiHandler::
HandleRefreshCertificateProvisioningProcesses,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"triggerCertificateProvisioningProcessUpdate",
base::BindRepeating(&CertificateProvisioningUiHandler::
HandleTriggerCertificateProvisioningProcessUpdate,
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::
HandleRefreshCertificateProvisioningProcesses(const base::ListValue* args) {
CHECK_EQ(0U, args->GetSize());
AllowJavascript();
RefreshCertificateProvisioningProcesses();
}
void CertificateProvisioningUiHandler::
HandleTriggerCertificateProvisioningProcessUpdate(
const base::ListValue* args) {
CHECK_EQ(2U, args->GetSize());
if (!args->is_list())
return;
const base::Value& cert_profile_id = args->GetList()[0];
if (!cert_profile_id.is_string())
return;
const base::Value& device_wide = args->GetList()[1];
if (!device_wide.is_bool())
return;
if (device_wide.GetBool() && !scheduler_for_device_)
return;
CertProvisioningScheduler* scheduler =
device_wide.GetBool() ? scheduler_for_device_ : scheduler_for_user_;
if (!scheduler)
return;
scheduler->UpdateOneCert(cert_profile_id.GetString());
}
void CertificateProvisioningUiHandler::
RefreshCertificateProvisioningProcesses() {
base::ListValue all_processes;
if (scheduler_for_user_) {
CollectProvisioningProcesses(&all_processes, scheduler_for_user_,
/*is_device_wide=*/false);
}
if (scheduler_for_device_) {
CollectProvisioningProcesses(&all_processes, scheduler_for_device_,
/*is_device_wide=*/true);
}
++ui_refresh_count_for_testing_;
FireWebUIListener("certificate-provisioning-processes-changed",
std::move(all_processes));
}
void CertificateProvisioningUiHandler::OnHoldBackUpdatesTimerExpired() {
if (update_after_hold_back_) {
update_after_hold_back_ = false;
RefreshCertificateProvisioningProcesses();
}
}
// static
bool CertificateProvisioningUiHandler::ShouldUseDeviceWideProcesses(
Profile* user_profile) {
const user_manager::User* user =
chromeos::ProfileHelper::Get()->GetUserByProfile(user_profile);
return user && user->IsAffiliated();
}
} // namespace cert_provisioning
} // namespace chromeos