// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/extensions/telemetry/api/common/api_guard_delegate.h"
#include <optional>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/containers/queue.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/values.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chromeos/extensions/telemetry/api/common/hardware_info_delegate.h"
#include "chrome/browser/chromeos/extensions/telemetry/api/common/util.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chromeos/extensions/chromeos_system_extension_info.h"
#include "extensions/common/extension.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "components/account_id/account_id.h" // nogncheck
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "chromeos/startup/browser_params_proxy.h"
#include "components/policy/core/common/policy_loader_lacros.h"
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
namespace content {
class BrowserContext;
}
namespace chromeos {
namespace switches {
// Skips the check for the device manufacturer.
// Used for development/testing.
const char kTelemetryExtensionSkipManufacturerCheckForTesting[] =
"telemetry-extension-skip-manufacturer-check-for-testing";
} // namespace switches
namespace {
using CheckCallback = base::OnceCallback<void(bool)>;
// A helper class to check some conditions asynchronously. All the checks will
// be performed sequentially. If any checker returns false, the rest won't be
// executed and `result_callback` will be called with a corresponding
// `error_message`. After checking all the conditions, `result_callback` is
// called with nullopt.
class AsyncConditionChecker {
public:
explicit AsyncConditionChecker(
base::OnceCallback<void(std::optional<std::string>)> result_callback);
AsyncConditionChecker(AsyncConditionChecker&) = delete;
AsyncConditionChecker& operator=(AsyncConditionChecker&) = delete;
~AsyncConditionChecker();
// Appends a checker and an error message to return when check fails. A
// checker is a `OnceCallback`. It should takes a callback and reply a boolean
// as result to it.
void AppendChecker(base::OnceCallback<void(CheckCallback)> checker,
const std::string& error_message);
// Same as above, but also accepts a sync checker for convenience. A sync
// checker is a `OnceCallback` which returns a boolean as result (instead of
// passing the result to a callback).
void AppendChecker(base::OnceCallback<bool(void)> checker,
const std::string& error_message);
// Starts the check.
void Run();
private:
void OnCheckFinished(const std::string& error_message, bool result);
base::OnceCallback<void(std::optional<std::string>)> result_callback_;
base::queue<std::pair<base::OnceCallback<void(CheckCallback)>, std::string>>
callback_queue_;
base::WeakPtrFactory<AsyncConditionChecker> weak_factory_{this};
};
AsyncConditionChecker::AsyncConditionChecker(
base::OnceCallback<void(std::optional<std::string>)> result_callback)
: result_callback_(std::move(result_callback)) {}
AsyncConditionChecker::~AsyncConditionChecker() = default;
void AsyncConditionChecker::AppendChecker(
base::OnceCallback<void(CheckCallback)> checker,
const std::string& error_message) {
callback_queue_.push(std::make_pair(std::move(checker), error_message));
}
void AsyncConditionChecker::AppendChecker(
base::OnceCallback<bool(void)> checker,
const std::string& error_message) {
callback_queue_.push(std::make_pair(
base::BindOnce(
[](base::OnceCallback<bool(void)> checker, CheckCallback check) {
std::move(checker).Then(std::move(check)).Run();
},
std::move(checker)),
error_message));
}
void AsyncConditionChecker::Run() {
if (callback_queue_.empty()) {
std::move(result_callback_).Run(std::nullopt);
return;
}
auto [checker, error_message] = std::move(callback_queue_.front());
callback_queue_.pop();
std::move(checker).Run(base::BindOnce(&AsyncConditionChecker::OnCheckFinished,
weak_factory_.GetWeakPtr(),
error_message));
}
void AsyncConditionChecker::OnCheckFinished(const std::string& error_message,
bool result) {
if (!result) {
std::move(result_callback_).Run(error_message);
return;
}
Run();
}
bool IsExtensionForceInstalled(content::BrowserContext* context,
const std::string& extension_id) {
const auto force_install_list =
extensions::ExtensionManagementFactory::GetForBrowserContext(context)
->GetForceInstallList();
return force_install_list.Find(extension_id) != nullptr;
}
void OnGetManufacturer(const std::string& extension_id,
CheckCallback callback,
const std::string& actual_manufacturer) {
const auto& extension_info = GetChromeOSExtensionInfoById(extension_id);
const auto& expected_manufacturers = extension_info.manufacturers;
std::move(callback).Run(expected_manufacturers.contains(actual_manufacturer));
}
void IsExpectedManufacturerForExtensionId(const std::string& extension_id,
CheckCallback callback) {
auto& hardware_info_delegate = HardwareInfoDelegate::Get();
hardware_info_delegate.GetManufacturer(
base::BindOnce(&OnGetManufacturer, extension_id, std::move(callback)));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
bool IsExtensionUsedByShimlessRMA(content::BrowserContext* context) {
return ::ash::features::IsShimlessRMA3pDiagnosticsEnabled() &&
::ash::IsShimlessRmaAppBrowserContext(context);
}
bool IsCurrentUserAffiliated() {
auto* active_user = user_manager::UserManager::Get()->GetActiveUser();
CHECK(active_user);
return active_user->IsAffiliated();
}
void IsCurrentUserOwnerOnOwnerFetched(CheckCallback callback) {
std::move(callback).Run(
user_manager::UserManager::Get()->IsCurrentUserOwner());
}
void IsCurrentUserOwner(content::BrowserContext* context,
CheckCallback callback) {
auto on_owner_fetched = base::IgnoreArgs<const AccountId&>(
base::BindOnce(&IsCurrentUserOwnerOnOwnerFetched, std::move(callback)));
user_manager::UserManager::Get()->GetOwnerAccountIdAsync(
std::move(on_owner_fetched));
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
bool IsExtensionUsedByShimlessRMA(content::BrowserContext* context) {
// TODO(b/292227137): Shimless RMA App is not enabled in LaCrOS before
// migrating Shimless RMA to LaCrOS.
return false;
}
bool IsCurrentUserAffiliated() {
return policy::PolicyLoaderLacros::IsMainUserAffiliated();
}
bool IsCurrentUserOwner(content::BrowserContext* context) {
// In order to determine device ownership in LaCrOS, we need to check
// whether the current Ash user is the device owner (stored in
// browser init params) and if the current profile is the same profile
// as the one logged into Ash.
return BrowserParamsProxy::Get()->IsCurrentUserDeviceOwner() &&
Profile::FromBrowserContext(context)->IsMainProfile();
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
class ApiGuardDelegateImpl : public ApiGuardDelegate {
public:
ApiGuardDelegateImpl();
ApiGuardDelegateImpl(const ApiGuardDelegateImpl&) = delete;
ApiGuardDelegateImpl& operator=(const ApiGuardDelegateImpl&) = delete;
~ApiGuardDelegateImpl() override;
// ApiGuardDelegate:
// As agreed with the privacy team, telemetry APIs can be accessed if all the
// following constraints are satisfied:
// 1. The user is either:
// a. managed and the extension was force-installed via policy, or
// b. the user is the device owner, or
// c. the user is in the Shimless RMA flow.
// 2. The PWA UI associated with the extension must be opened.
// 3. The device hardware belongs to the OEM associated with the extension.
void CanAccessApi(content::BrowserContext* context,
const extensions::Extension* extension,
CanAccessApiCallback callback) override;
private:
std::unique_ptr<AsyncConditionChecker> condition_checker_;
};
ApiGuardDelegateImpl::ApiGuardDelegateImpl() = default;
ApiGuardDelegateImpl::~ApiGuardDelegateImpl() = default;
void ApiGuardDelegateImpl::CanAccessApi(content::BrowserContext* context,
const extensions::Extension* extension,
CanAccessApiCallback callback) {
condition_checker_ =
std::make_unique<AsyncConditionChecker>(std::move(callback));
// base::Unretained is safe to use for a pointer to Extension and Context,
// because both the extension and the context outlive the delegate by
// definition (since this is executed as part of an extension API call), and
// these callbacks are bound to the `condition_checker_`, which is bound to
// the delegate.
if (IsExtensionUsedByShimlessRMA(context)) {
// No user to check for the Shimless RMA flow. Note that in this
// case there is no active user in UserManager.
} else if (IsCurrentUserAffiliated()) {
condition_checker_->AppendChecker(
base::BindOnce(&IsExtensionForceInstalled, base::Unretained(context),
extension->id()),
"This extension is not installed by the admin");
} else {
condition_checker_->AppendChecker(
base::BindOnce(&IsCurrentUserOwner, base::Unretained(context)),
"This extension is not run by the device owner");
}
condition_checker_->AppendChecker(
base::BindOnce(&IsTelemetryExtensionAppUiOpenAndSecure,
base::Unretained(context), base::Unretained(extension)),
"Companion app UI is not open or not secure");
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kTelemetryExtensionSkipManufacturerCheckForTesting)) {
condition_checker_->AppendChecker(
base::BindOnce(&IsExpectedManufacturerForExtensionId, extension->id()),
"This extension is not allowed to access the API on this device");
}
condition_checker_->Run();
}
} // namespace
// static
ApiGuardDelegate::Factory* ApiGuardDelegate::Factory::test_factory_ = nullptr;
// static
std::unique_ptr<ApiGuardDelegate> ApiGuardDelegate::Factory::Create() {
if (test_factory_) {
return test_factory_->CreateInstance();
}
return base::WrapUnique<ApiGuardDelegate>(new ApiGuardDelegateImpl());
}
// static
void ApiGuardDelegate::Factory::SetForTesting(Factory* test_factory) {
test_factory_ = test_factory;
}
ApiGuardDelegate::Factory::Factory() = default;
ApiGuardDelegate::Factory::~Factory() = default;
ApiGuardDelegate::ApiGuardDelegate() = default;
ApiGuardDelegate::~ApiGuardDelegate() = default;
} // namespace chromeos