blob: 585175368f8610157b5872e96c5b10edb037a90d [file] [log] [blame]
// Copyright 2021 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 "chrome/browser/ash/plugin_vm/plugin_vm_diagnostics.h"
#include <string>
#include <utility>
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/values.h"
#include "chrome/browser/ash/guest_os/guest_os_diagnostics.mojom.h"
#include "chrome/browser/ash/guest_os/guest_os_diagnostics_builder.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_features.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/dbus/concierge/concierge_client.h"
#include "chromeos/dbus/concierge/concierge_service.pb.h"
#include "components/prefs/pref_service.h"
#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
#include "ui/base/l10n/l10n_util.h"
namespace plugin_vm {
namespace {
using DiagnosticsCallback =
base::OnceCallback<void(guest_os::mojom::DiagnosticsPtr)>;
std::string CapitalizedBoardName() {
const std::string uppercase = base::SysInfo::HardwareModelName();
CHECK_GE(uppercase.size(), 1);
base::StringPiece uppercase_first_char(uppercase.c_str(), 1);
base::StringPiece uppercase_remaining(uppercase.c_str() + 1,
uppercase.length() - 1);
return base::StrCat(
{uppercase_first_char, base::ToLowerASCII(uppercase_remaining)});
}
class PluginVmDiagnostics : public base::RefCounted<PluginVmDiagnostics> {
public:
static void Run(DiagnosticsCallback callback) {
// Kick off the first step. The object is kept alive in callbacks until it
// is finished.
base::WrapRefCounted(new PluginVmDiagnostics(std::move(callback)))
->CheckPluginVmIsAllowed();
}
private:
friend class base::RefCounted<PluginVmDiagnostics>;
using EntryBuilder = guest_os::DiagnosticsBuilder::EntryBuilder;
using ImageListType =
::google::protobuf::RepeatedPtrField<vm_tools::concierge::VmDiskInfo>;
explicit PluginVmDiagnostics(DiagnosticsCallback callback)
: active_profile_{ProfileManager::GetActiveUserProfile()},
callback_(std::move(callback)) {}
~PluginVmDiagnostics() { DCHECK(callback_.is_null()); }
void CheckPluginVmIsAllowed() {
using ProfileSupported = plugin_vm::PluginVmFeatures::ProfileSupported;
using PolicyConfigured = plugin_vm::PluginVmFeatures::PolicyConfigured;
auto is_allowed_diagnostics =
plugin_vm::PluginVmFeatures::Get()->GetIsAllowedDiagnostics(
active_profile_);
// TODO(b/173653141): Consider reconciling the error messages with
// `is_allowed_diagnostics.GetTopError()` so that we can reuse it.
{
EntryBuilder entry(IDS_VM_STATUS_PAGE_DEVICE_IS_SUPPORTED_REQUIREMENT);
if (!is_allowed_diagnostics.device_supported) {
entry.SetFail(l10n_util::GetStringFUTF8(
IDS_VM_STATUS_PAGE_DEVICE_IS_SUPPORTED_EXPLANATION,
base::UTF8ToUTF16(CapitalizedBoardName())));
}
builder_.AddEntry(std::move(entry));
}
{
EntryBuilder entry(IDS_VM_STATUS_PAGE_PROFILE_IS_SUPPORTED_REQUIREMENT);
switch (is_allowed_diagnostics.profile_supported) {
case ProfileSupported::kOk:
break;
case ProfileSupported::kErrorNonPrimary:
entry.SetFail(IDS_VM_STATUS_PAGE_SECONDARY_PROFILE_EXPLANATION);
break;
case ProfileSupported::kErrorChildAccount:
entry.SetFail(IDS_VM_STATUS_PAGE_CHILD_PROFILE_EXPLANATION);
break;
case ProfileSupported::kErrorOffTheRecord:
entry.SetFail(IDS_VM_STATUS_PAGE_GUEST_PROFILE_EXPLANATION);
break;
case ProfileSupported::kErrorEphemeral:
entry.SetFail(
IDS_VM_STATUS_PAGE_EPHEMERAL_PROFILE_EXPLANATION,
/*learn_more_link=*/
GURL("https://support.google.com/chromebook?p=ephemeral_mode"));
break;
case ProfileSupported::kErrorNotSupported:
entry.SetFail(IDS_VM_STATUS_PAGE_UNSUPPORTED_PROFILE_EXPLANATION);
break;
}
builder_.AddEntry(std::move(entry));
}
{
EntryBuilder entry(IDS_VM_STATUS_PAGE_POLICIES_REQUIREMENT);
bool set_standard_top_error = true;
switch (is_allowed_diagnostics.policy_configured) {
case PolicyConfigured::kOk: {
// Additional check for image policy. See b/185281662#comment2.
const base::Value* image_policy =
active_profile_->GetPrefs()->GetDictionary(prefs::kPluginVmImage);
const base::Value* url =
image_policy->FindKey(prefs::kPluginVmImageUrlKeyName);
const base::Value* hash =
image_policy->FindKey(prefs::kPluginVmImageHashKeyName);
if (!url || !GURL(url->GetString()).is_valid()) {
entry.SetFail(IDS_VM_STATUS_PAGE_IMAGE_URL_POLICY_EXPLANATION);
} else if (!hash || hash->GetString().empty()) {
entry.SetFail(IDS_VM_STATUS_PAGE_IMAGE_HASH_POLICY_EXPLANATION);
} else {
set_standard_top_error = false;
}
} break;
case PolicyConfigured::kErrorUnableToCheckPolicy:
entry.SetFail(
IDS_VM_STATUS_PAGE_UNABLE_TO_CHECK_POLICIES_EXPLANATION);
break;
case PolicyConfigured::kErrorNotEnterpriseEnrolled:
entry.SetFail(IDS_VM_STATUS_PAGE_DEVICE_NOT_ENROLLED_EXPLANATION)
.OverrideTopError(
IDS_VM_STATUS_PAGE_DEVICE_NOT_ENROLLED_ERROR,
/*learn_more_link=*/GURL(
"https://support.google.com/chromebook?p=enroll_device"));
set_standard_top_error = false;
break;
case PolicyConfigured::kErrorUserNotAffiliated:
entry.SetFail(IDS_VM_STATUS_PAGE_USER_NOT_AFFILIATED_EXPLANATION);
break;
case PolicyConfigured::kErrorUnableToCheckDevicePolicy:
entry.SetFail(
IDS_VM_STATUS_PAGE_UNABLE_TO_CHECK_DEVICE_ALLOW_EXPLANATION);
break;
case PolicyConfigured::kErrorNotAllowedByDevicePolicy:
entry.SetFail(IDS_VM_STATUS_PAGE_DEVICE_NOT_ALLOW_EXPLANATION);
break;
case PolicyConfigured::kErrorNotAllowedByUserPolicy:
entry.SetFail(IDS_VM_STATUS_PAGE_USER_NOT_ALLOW_EXPLANATION);
break;
case PolicyConfigured::kErrorLicenseNotSetUp:
entry.SetFail(IDS_VM_STATUS_PAGE_LICENSE_NOT_SET_UP_EXPLANATION);
break;
}
if (set_standard_top_error) {
entry.OverrideTopError(IDS_VM_STATUS_PAGE_INCORRECT_POLICIES_ERROR);
}
builder_.AddEntry(std::move(entry));
}
// Next step.
CheckDefaultVmExists(is_allowed_diagnostics.IsOk());
}
void CheckDefaultVmExists(bool plugin_vm_is_allowed) {
if (!plugin_vm_is_allowed) {
OnListVmDisks(false, absl::nullopt);
return;
}
vm_tools::concierge::ListVmDisksRequest request;
request.set_cryptohome_id(
ash::ProfileHelper::GetUserIdHashFromProfile(active_profile_));
request.set_storage_location(
vm_tools::concierge::STORAGE_CRYPTOHOME_PLUGINVM);
chromeos::ConciergeClient::Get()->ListVmDisks(
std::move(request),
base::BindOnce(&PluginVmDiagnostics::OnListVmDisks, this,
/*plugin_vm_is_allowed=*/true));
}
void OnListVmDisks(
bool plugin_vm_is_allowed,
absl::optional<vm_tools::concierge::ListVmDisksResponse> response) {
EntryBuilder entry(l10n_util::GetStringFUTF8(
IDS_VM_STATUS_PAGE_DEFAULT_VM_EXISTS_REQUIREMENT,
l10n_util::GetStringUTF16(IDS_PLUGIN_VM_APP_NAME)));
if (plugin_vm_is_allowed) {
if (!response.has_value()) {
entry.SetFail(IDS_VM_STATUS_PAGE_FAILED_TO_CHECK_VM_EXPLANATION);
} else if (!HasDefaultVm(response->images())) {
entry.SetFail(GetMissingDefaultVmExplanation(response->images()))
.OverrideTopError(
l10n_util::GetStringFUTF8(
IDS_VM_STATUS_PAGE_MISSING_DEFAULT_VM_ERROR,
l10n_util::GetStringUTF16(IDS_PLUGIN_VM_APP_NAME)),
/*learn_more_link=*/
GURL(
"https://support.google.com/chromebook?p=parallels_setup"));
} else {
// Everything is good. Do nothing.
}
} else { // !plugin_vm_is_allowed.
entry.SetNotApplicable();
}
builder_.AddEntry(std::move(entry));
Finish();
}
void Finish() {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), builder_.Build()));
}
bool HasDefaultVm(const ImageListType& images) {
for (auto& image : images) {
if (image.name() == plugin_vm::kPluginVmName) {
return true;
}
}
return false;
}
std::string GetMissingDefaultVmExplanation(const ImageListType& images) {
std::string string_template = l10n_util::GetPluralStringFUTF8(
IDS_VM_STATUS_PAGE_MISSING_DEFAULT_VM_EXPLANATION, images.size());
std::vector<std::string> subs{
l10n_util::GetStringUTF8(IDS_PLUGIN_VM_APP_NAME)};
if (images.size() > 0) {
// In this case, we have a second placeholder VM_NAME_LIST. The substitute
// should looke like this: `"vm1", "vm2"`. Note that the l10n tooling does
// not support formatting a list of strings, which is why we have to do
// the formatting by ourselves here. The formatting might not be ideal for
// all languages, but it should be good enough for its purpose.
std::stringstream stream;
bool first_vm = true;
for (auto& image : images) {
if (!first_vm) {
stream << ", ";
}
stream << '"' << image.name() << '"';
first_vm = false;
}
subs.push_back(stream.str());
}
return base::ReplaceStringPlaceholders(string_template, subs, nullptr);
}
Profile* const active_profile_;
DiagnosticsCallback callback_;
guest_os::DiagnosticsBuilder builder_;
};
} // namespace
void GetDiagnostics(
base::OnceCallback<void(guest_os::mojom::DiagnosticsPtr)> callback) {
PluginVmDiagnostics::Run(std::move(callback));
}
} // namespace plugin_vm