diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-09-29 16:16:15 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-11-09 10:04:06 +0000 |
commit | a95a7417ad456115a1ef2da4bb8320531c0821f1 (patch) | |
tree | edcd59279e486d2fd4a8f88a7ed025bcf925c6e6 /chromium/components/device_signals | |
parent | 33fc33aa94d4add0878ec30dc818e34e1dd3cc2a (diff) | |
download | qtwebengine-chromium-a95a7417ad456115a1ef2da4bb8320531c0821f1.tar.gz |
BASELINE: Update Chromium to 106.0.5249.126
Change-Id: Ib0bb21c437a7d1686e21c33f2d329f2ac425b7ab
Reviewed-on: https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/438936
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/device_signals')
104 files changed, 4037 insertions, 863 deletions
diff --git a/chromium/components/device_signals/DEPS b/chromium/components/device_signals/DEPS new file mode 100644 index 00000000000..a740940b4da --- /dev/null +++ b/chromium/components/device_signals/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+crypto", +]
\ No newline at end of file diff --git a/chromium/components/device_signals/core/BUILD.gn b/chromium/components/device_signals/core/BUILD.gn index 3c3c530e82f..0fc3e5256de 100644 --- a/chromium/components/device_signals/core/BUILD.gn +++ b/chromium/components/device_signals/core/BUILD.gn @@ -7,5 +7,6 @@ source_set("unit_tests") { deps = [ "//components/device_signals/core/browser:unit_tests", "//components/device_signals/core/common:unit_tests", + "//components/device_signals/core/system_signals:unit_tests", ] } diff --git a/chromium/components/device_signals/core/browser/BUILD.gn b/chromium/components/device_signals/core/browser/BUILD.gn index a12e34d6503..abfd3a2d7cd 100644 --- a/chromium/components/device_signals/core/browser/BUILD.gn +++ b/chromium/components/device_signals/core/browser/BUILD.gn @@ -4,9 +4,13 @@ static_library("browser") { public = [ + "base_signals_collector.h", + "file_system_signals_collector.h", + "metrics_utils.h", "signals_aggregator.h", "signals_aggregator_impl.h", "signals_collector.h", + "signals_types.h", "system_signals_service_host.h", "user_context.h", "user_delegate.h", @@ -15,7 +19,11 @@ static_library("browser") { ] sources = [ + "base_signals_collector.cc", + "file_system_signals_collector.cc", + "metrics_utils.cc", "signals_aggregator_impl.cc", + "signals_types.cc", "user_context.cc", "user_permission_service_impl.cc", ] @@ -24,18 +32,31 @@ static_library("browser") { "//base", "//components/device_signals/core/common/mojom", "//components/keyed_service/core", + "//third_party/abseil-cpp:absl", ] deps = [ "//components/device_signals/core/common", "//components/policy/core/common", + "//components/prefs", "//components/signin/public/identity_manager", ] + + if (is_win) { + public_deps += [ "//components/device_signals/core/common/win" ] + } + + if (is_win || is_linux || is_mac) { + public += [ "pref_names.h" ] + sources += [ "pref_names.cc" ] + } } static_library("test_support") { testonly = true sources = [ + "mock_signals_aggregator.cc", + "mock_signals_aggregator.h", "mock_signals_collector.cc", "mock_signals_collector.h", "mock_system_signals_service_host.cc", @@ -58,6 +79,7 @@ static_library("test_support") { source_set("unit_tests") { testonly = true sources = [ + "file_system_signals_collector_unittest.cc", "signals_aggregator_impl_unittest.cc", "user_permission_service_impl_unittest.cc", ] diff --git a/chromium/components/device_signals/core/browser/DEPS b/chromium/components/device_signals/core/browser/DEPS index 97d436ae7d8..b5db53237dd 100644 --- a/chromium/components/device_signals/core/browser/DEPS +++ b/chromium/components/device_signals/core/browser/DEPS @@ -2,4 +2,5 @@ include_rules = [ "+components/keyed_service/core", "+components/signin/public", "+components/policy/core", + "+components/prefs", ] diff --git a/chromium/components/device_signals/core/browser/base_signals_collector.cc b/chromium/components/device_signals/core/browser/base_signals_collector.cc new file mode 100644 index 00000000000..584f9af9823 --- /dev/null +++ b/chromium/components/device_signals/core/browser/base_signals_collector.cc @@ -0,0 +1,53 @@ +// Copyright 2022 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 "components/device_signals/core/browser/base_signals_collector.h" + +#include <unordered_set> +#include <utility> + +#include "base/callback.h" +#include "components/device_signals/core/browser/signals_types.h" + +namespace device_signals { + +BaseSignalsCollector::BaseSignalsCollector( + const std::unordered_map<const SignalName, GetSignalCallback> + signals_collection_map) + : signals_collection_map_(std::move(signals_collection_map)) { + DCHECK(!signals_collection_map_.empty()); +} + +BaseSignalsCollector::~BaseSignalsCollector() = default; + +bool BaseSignalsCollector::IsSignalSupported(SignalName signal_name) { + const auto it = signals_collection_map_.find(signal_name); + return it != signals_collection_map_.end(); +} + +const std::unordered_set<SignalName> +BaseSignalsCollector::GetSupportedSignalNames() { + std::unordered_set<SignalName> supported_signals; + for (const auto& collection_pair : signals_collection_map_) { + supported_signals.insert(collection_pair.first); + } + + return supported_signals; +} + +void BaseSignalsCollector::GetSignal(SignalName signal_name, + const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure) { + if (!IsSignalSupported(signal_name)) { + response.top_level_error = SignalCollectionError::kUnsupported; + std::move(done_closure).Run(); + return; + } + + signals_collection_map_[signal_name].Run(request, response, + std::move(done_closure)); +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/base_signals_collector.h b/chromium/components/device_signals/core/browser/base_signals_collector.h new file mode 100644 index 00000000000..82b9696af6d --- /dev/null +++ b/chromium/components/device_signals/core/browser/base_signals_collector.h @@ -0,0 +1,46 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_BASE_SIGNALS_COLLECTOR_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_BASE_SIGNALS_COLLECTOR_H_ + +#include <unordered_map> + +#include "base/callback.h" +#include "components/device_signals/core/browser/signals_collector.h" + +namespace device_signals { + +class BaseSignalsCollector : public SignalsCollector { + public: + ~BaseSignalsCollector() override; + + // SignalsCollector: + bool IsSignalSupported(SignalName signal_name) override; + const std::unordered_set<SignalName> GetSupportedSignalNames() override; + void GetSignal(SignalName signal_name, + const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure) override; + + protected: + using GetSignalCallback = + base::RepeatingCallback<void(const SignalsAggregationRequest&, + SignalsAggregationResponse&, + base::OnceClosure)>; + + explicit BaseSignalsCollector( + const std::unordered_map<const SignalName, GetSignalCallback> + signals_collection_map); + + private: + // Map used to forward signal collection requests to the right function keyed + // from a given signal name. + std::unordered_map<const SignalName, GetSignalCallback> + signals_collection_map_; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_BASE_SIGNALS_COLLECTOR_H_ diff --git a/chromium/components/device_signals/core/browser/file_system_signals_collector.cc b/chromium/components/device_signals/core/browser/file_system_signals_collector.cc new file mode 100644 index 00000000000..b7e529d0517 --- /dev/null +++ b/chromium/components/device_signals/core/browser/file_system_signals_collector.cc @@ -0,0 +1,73 @@ +// Copyright 2022 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 "components/device_signals/core/browser/file_system_signals_collector.h" + +#include <utility> + +#include "base/bind.h" +#include "base/check.h" +#include "components/device_signals/core/browser/signals_types.h" +#include "components/device_signals/core/browser/system_signals_service_host.h" +#include "components/device_signals/core/common/common_types.h" +#include "components/device_signals/core/common/mojom/system_signals.mojom.h" + +namespace device_signals { + +FileSystemSignalsCollector::FileSystemSignalsCollector( + SystemSignalsServiceHost* system_service_host) + : BaseSignalsCollector({ + {SignalName::kFileSystemInfo, + base::BindRepeating( + &FileSystemSignalsCollector::GetFileSystemInfoSignal, + base::Unretained(this))}, + }), + system_service_host_(system_service_host) { + DCHECK(system_service_host_); +} + +FileSystemSignalsCollector::~FileSystemSignalsCollector() = default; + +void FileSystemSignalsCollector::GetFileSystemInfoSignal( + const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure) { + if (request.file_system_signal_parameters.empty()) { + FileSystemInfoResponse signal_response; + signal_response.collection_error = + SignalCollectionError::kMissingParameters; + response.file_system_info_response = std::move(signal_response); + std::move(done_closure).Run(); + return; + } + + auto* system_signals_service = system_service_host_->GetService(); + if (!system_signals_service) { + FileSystemInfoResponse signal_response; + signal_response.collection_error = + SignalCollectionError::kMissingSystemService; + response.file_system_info_response = std::move(signal_response); + std::move(done_closure).Run(); + return; + } + + system_signals_service->GetFileSystemSignals( + request.file_system_signal_parameters, + base::BindOnce(&FileSystemSignalsCollector::OnFileSystemSignalCollected, + weak_factory_.GetWeakPtr(), std::ref(response), + std::move(done_closure))); +} + +void FileSystemSignalsCollector::OnFileSystemSignalCollected( + SignalsAggregationResponse& response, + base::OnceClosure done_closure, + const std::vector<FileSystemItem>& file_system_items) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + FileSystemInfoResponse signal_response; + signal_response.file_system_items = std::move(file_system_items); + response.file_system_info_response = std::move(signal_response); + std::move(done_closure).Run(); +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/file_system_signals_collector.h b/chromium/components/device_signals/core/browser/file_system_signals_collector.h new file mode 100644 index 00000000000..0b70b429a90 --- /dev/null +++ b/chromium/components/device_signals/core/browser/file_system_signals_collector.h @@ -0,0 +1,60 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_FILE_SYSTEM_SIGNALS_COLLECTOR_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_FILE_SYSTEM_SIGNALS_COLLECTOR_H_ + +#include <map> +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "base/sequence_checker.h" +#include "components/device_signals/core/browser/base_signals_collector.h" + +namespace device_signals { + +struct FileSystemItem; +class SystemSignalsServiceHost; + +// Collector in charge of collecting device signals that live in the file +// system. +class FileSystemSignalsCollector : public BaseSignalsCollector { + public: + explicit FileSystemSignalsCollector( + SystemSignalsServiceHost* system_service_host); + + ~FileSystemSignalsCollector() override; + + FileSystemSignalsCollector(const FileSystemSignalsCollector&) = delete; + FileSystemSignalsCollector& operator=(const FileSystemSignalsCollector&) = + delete; + + private: + // Collection function for the File System Info signal. `request` must contain + // the required parameters for this signal. `response` will be passed along + // and the signal values will be set on it when available. `done_closure` will + // be invoked when signal collection is complete. + void GetFileSystemInfoSignal(const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure); + + // Invoked when the SystemSignalsService returns the collected File System + // items' signals as `file_system_items`. Will update `response` with the + // signal collection outcome, and invoke `done_closure` to asynchronously + // notify the caller of the completion of this request. + void OnFileSystemSignalCollected( + SignalsAggregationResponse& response, + base::OnceClosure done_closure, + const std::vector<FileSystemItem>& file_system_items); + + // Instance used to retrieve a pointer to a SystemSignalsService instance. + base::raw_ptr<SystemSignalsServiceHost> system_service_host_; + + SEQUENCE_CHECKER(sequence_checker_); + base::WeakPtrFactory<FileSystemSignalsCollector> weak_factory_{this}; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_FILE_SYSTEM_SIGNALS_COLLECTOR_H_ diff --git a/chromium/components/device_signals/core/browser/file_system_signals_collector_unittest.cc b/chromium/components/device_signals/core/browser/file_system_signals_collector_unittest.cc new file mode 100644 index 00000000000..409be2fa098 --- /dev/null +++ b/chromium/components/device_signals/core/browser/file_system_signals_collector_unittest.cc @@ -0,0 +1,182 @@ +// Copyright 2022 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 "components/device_signals/core/browser/file_system_signals_collector.h" + +#include <array> +#include <utility> + +#include "base/files/file_path.h" +#include "base/run_loop.h" +#include "base/test/task_environment.h" +#include "base/values.h" +#include "components/device_signals/core/browser/mock_system_signals_service_host.h" +#include "components/device_signals/core/browser/signals_types.h" +#include "components/device_signals/core/common/common_types.h" +#include "components/device_signals/core/common/signals_constants.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::ContainerEq; +using testing::Invoke; +using testing::Return; +using testing::StrictMock; + +namespace device_signals { + +namespace { + +SignalsAggregationRequest CreateRequest(SignalName signal_name, + bool with_file_parameter = true) { + SignalsAggregationRequest request; + request.signal_names.emplace(signal_name); + + if (with_file_parameter) { + GetFileSystemInfoOptions options1; + options1.file_path = base::FilePath::FromUTF8Unsafe("some file path"); + options1.compute_sha256 = true; + options1.compute_executable_metadata = true; + + GetFileSystemInfoOptions options2; + options2.file_path = base::FilePath::FromUTF8Unsafe("some file path"); + options2.compute_sha256 = true; + options2.compute_executable_metadata = true; + + request.file_system_signal_parameters.push_back(options1); + request.file_system_signal_parameters.push_back(options2); + } + + return request; +} +} // namespace + +using GetFileSystemSignalsCallback = + MockSystemSignalsService::GetFileSystemSignalsCallback; + +class FileSystemSignalsCollectorTest : public testing::Test { + protected: + FileSystemSignalsCollectorTest() : signal_collector_(&service_host_) { + ON_CALL(service_host_, GetService()).WillByDefault(Return(&service_)); + } + + base::test::TaskEnvironment task_environment_; + + StrictMock<MockSystemSignalsServiceHost> service_host_; + StrictMock<MockSystemSignalsService> service_; + FileSystemSignalsCollector signal_collector_; +}; + +// Test that runs a sanity check on the set of signals supported by this +// collector. Will need to be updated if new signals become supported. +TEST_F(FileSystemSignalsCollectorTest, SupportedSignalNames) { + const std::array<SignalName, 1> supported_signals{ + {SignalName::kFileSystemInfo}}; + + const auto names_set = signal_collector_.GetSupportedSignalNames(); + + EXPECT_EQ(names_set.size(), supported_signals.size()); + for (const auto& signal_name : supported_signals) { + EXPECT_TRUE(names_set.find(signal_name) != names_set.end()); + } +} + +// Tests that an unsupported signal is marked as unsupported. +TEST_F(FileSystemSignalsCollectorTest, GetSignal_Unsupported) { + SignalName signal_name = SignalName::kAntiVirus; + SignalsAggregationResponse response; + base::RunLoop run_loop; + signal_collector_.GetSignal(signal_name, CreateRequest(signal_name), response, + run_loop.QuitClosure()); + + run_loop.Run(); + + ASSERT_TRUE(response.top_level_error.has_value()); + EXPECT_EQ(response.top_level_error.value(), + SignalCollectionError::kUnsupported); +} + +// Tests that the request does not contain the required parameters for the +// File System signal. +TEST_F(FileSystemSignalsCollectorTest, GetSignal_File_MissingParameters) { + SignalName signal_name = SignalName::kFileSystemInfo; + SignalsAggregationResponse response; + base::RunLoop run_loop; + signal_collector_.GetSignal( + signal_name, CreateRequest(signal_name, /*with_file_parameter=*/false), + response, run_loop.QuitClosure()); + + run_loop.Run(); + + ASSERT_FALSE(response.top_level_error.has_value()); + ASSERT_TRUE(response.file_system_info_response.has_value()); + ASSERT_TRUE(response.file_system_info_response->collection_error.has_value()); + EXPECT_EQ(response.file_system_info_response->collection_error.value(), + SignalCollectionError::kMissingParameters); +} + +// Tests that not being able to retrieve a pointer to the SystemSignalsService +// returns an error. +TEST_F(FileSystemSignalsCollectorTest, + GetSignal_File_MissingSystemSignalsService) { + EXPECT_CALL(service_host_, GetService()).WillOnce(Return(nullptr)); + + SignalName signal_name = SignalName::kFileSystemInfo; + SignalsAggregationResponse response; + base::RunLoop run_loop; + signal_collector_.GetSignal(signal_name, CreateRequest(signal_name), response, + run_loop.QuitClosure()); + + run_loop.Run(); + + ASSERT_FALSE(response.top_level_error.has_value()); + ASSERT_TRUE(response.file_system_info_response.has_value()); + ASSERT_TRUE(response.file_system_info_response->collection_error.has_value()); + EXPECT_EQ(response.file_system_info_response->collection_error.value(), + SignalCollectionError::kMissingSystemService); +} + +// Tests a successful File System signal retrieval. +TEST_F(FileSystemSignalsCollectorTest, GetSignal_FileSystemInfo) { + // Can be any value really. + FileSystemItem retrieved_item; + retrieved_item.file_path = + base::FilePath::FromUTF8Unsafe("some successful file path"); + retrieved_item.presence = PresenceValue::kFound; + + std::vector<FileSystemItem> file_system_items; + file_system_items.push_back(retrieved_item); + + SignalName signal_name = SignalName::kFileSystemInfo; + auto request = CreateRequest(signal_name); + + EXPECT_CALL(service_host_, GetService()).Times(1); + EXPECT_CALL(service_, + GetFileSystemSignals( + ContainerEq(request.file_system_signal_parameters), _)) + .WillOnce(Invoke( + [&file_system_items]( + const std::vector<GetFileSystemInfoOptions> signal_parameters, + GetFileSystemSignalsCallback signal_callback) { + std::move(signal_callback).Run(file_system_items); + })); + + SignalsAggregationResponse response; + base::RunLoop run_loop; + signal_collector_.GetSignal(signal_name, request, response, + run_loop.QuitClosure()); + + run_loop.Run(); + + EXPECT_FALSE(response.top_level_error.has_value()); + ASSERT_TRUE(response.file_system_info_response.has_value()); + EXPECT_FALSE( + response.file_system_info_response->collection_error.has_value()); + EXPECT_EQ(response.file_system_info_response->file_system_items.size(), + file_system_items.size()); + EXPECT_EQ(response.file_system_info_response->file_system_items[0], + file_system_items[0]); +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/metrics_utils.cc b/chromium/components/device_signals/core/browser/metrics_utils.cc new file mode 100644 index 00000000000..02d955c1f2a --- /dev/null +++ b/chromium/components/device_signals/core/browser/metrics_utils.cc @@ -0,0 +1,86 @@ +// Copyright 2022 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 "components/device_signals/core/browser/metrics_utils.h" + +#include "base/metrics/histogram_functions.h" +#include "base/strings/stringprintf.h" +#include "components/device_signals/core/browser/signals_types.h" +#include "components/device_signals/core/browser/user_permission_service.h" + +namespace device_signals { + +namespace { + +constexpr int kMaxSampleValue = 100; + +constexpr char kUserPermissionHistogram[] = + "Enterprise.DeviceSignals.UserPermission"; +constexpr char kCollectionRequestHistogram[] = + "Enterprise.DeviceSignals.Collection.Request"; +constexpr char kCollectionSuccessHistogram[] = + "Enterprise.DeviceSignals.Collection.Success"; +constexpr char kCollectionFailureHistogram[] = + "Enterprise.DeviceSignals.Collection.Failure"; + +constexpr char kCollectionSuccessSizeHistogramFormat[] = + "Enterprise.DeviceSignals.Collection.Success.%s.Items"; +constexpr char kCollectionSpecificFailureHistogramFormat[] = + "Enterprise.DeviceSignals.Collection.Failure.%sLevelError"; + +std::string GetHistogramVariant(SignalName signal_name) { + switch (signal_name) { + case SignalName::kAntiVirus: + return "AntiVirus"; + case SignalName::kHotfixes: + return "Hotfixes"; + case SignalName::kFileSystemInfo: + return "FileSystemInfo"; + case SignalName::kSystemSettings: + return "SystemSettings"; + } +} + +std::string GetErrorHistogramVariant(SignalName signal_name, + bool is_top_level_error) { + return base::StringPrintf("%s.%s", GetHistogramVariant(signal_name).c_str(), + is_top_level_error ? "Top" : "Collection"); +} + +} // namespace + +void LogUserPermissionChecked(UserPermission permission) { + base::UmaHistogramEnumeration(kUserPermissionHistogram, permission); +} + +void LogSignalCollectionRequested(SignalName signal_name) { + base::UmaHistogramEnumeration(kCollectionRequestHistogram, signal_name); +} + +void LogSignalCollectionFailed(SignalName signal_name, + SignalCollectionError error, + bool is_top_level_error) { + base::UmaHistogramEnumeration(kCollectionFailureHistogram, signal_name); + + base::UmaHistogramEnumeration( + base::StringPrintf( + kCollectionSpecificFailureHistogramFormat, + GetErrorHistogramVariant(signal_name, is_top_level_error).c_str()), + error); +} + +void LogSignalCollectionSucceeded( + SignalName signal_name, + absl::optional<size_t> signal_collection_size) { + base::UmaHistogramEnumeration(kCollectionSuccessHistogram, signal_name); + + if (signal_collection_size.has_value()) { + base::UmaHistogramExactLinear( + base::StringPrintf(kCollectionSuccessSizeHistogramFormat, + GetHistogramVariant(signal_name).c_str()), + signal_collection_size.value(), kMaxSampleValue); + } +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/metrics_utils.h b/chromium/components/device_signals/core/browser/metrics_utils.h new file mode 100644 index 00000000000..67fbc59af3b --- /dev/null +++ b/chromium/components/device_signals/core/browser/metrics_utils.h @@ -0,0 +1,38 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_METRICS_UTILS_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_METRICS_UTILS_H_ + +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace device_signals { + +enum class SignalCollectionError; +enum class SignalName; +enum class UserPermission; + +// Records that `permission` was the outcome of a permission check. +void LogUserPermissionChecked(UserPermission permission); + +// Records that a request to collect `signal_name` was received. +void LogSignalCollectionRequested(SignalName signal_name); + +// Records that the collection of the signal `signal_name` failed with `error`, +// which, based on `is_top_level_error`, is either a top-level aggregation +// error, or a collection-level error. +void LogSignalCollectionFailed(SignalName signal_name, + SignalCollectionError error, + bool is_top_level_error); + +// Records that the collection of the signal `signal_name` was successful. +// If applicable, will also record the number of items collected as given by +// `signal_collection_size`. +void LogSignalCollectionSucceeded( + SignalName signal_name, + absl::optional<size_t> signal_collection_size); + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_METRICS_UTILS_H_ diff --git a/chromium/components/device_signals/core/browser/mock_signals_aggregator.cc b/chromium/components/device_signals/core/browser/mock_signals_aggregator.cc new file mode 100644 index 00000000000..46db9f5c545 --- /dev/null +++ b/chromium/components/device_signals/core/browser/mock_signals_aggregator.cc @@ -0,0 +1,12 @@ +// Copyright 2022 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 "components/device_signals/core/browser/mock_signals_aggregator.h" + +namespace device_signals { + +MockSignalsAggregator::MockSignalsAggregator() = default; +MockSignalsAggregator::~MockSignalsAggregator() = default; + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/mock_signals_aggregator.h b/chromium/components/device_signals/core/browser/mock_signals_aggregator.h new file mode 100644 index 00000000000..77284d91452 --- /dev/null +++ b/chromium/components/device_signals/core/browser/mock_signals_aggregator.h @@ -0,0 +1,29 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_MOCK_SIGNALS_AGGREGATOR_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_MOCK_SIGNALS_AGGREGATOR_H_ + +#include "base/callback.h" +#include "components/device_signals/core/browser/signals_aggregator.h" +#include "components/device_signals/core/browser/signals_types.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace device_signals { + +class MockSignalsAggregator : public SignalsAggregator { + public: + MockSignalsAggregator(); + ~MockSignalsAggregator() override; + + MOCK_METHOD(void, + GetSignals, + (const SignalsAggregationRequest&, + SignalsAggregator::GetSignalsCallback), + (override)); +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_MOCK_SIGNALS_AGGREGATOR_H_ diff --git a/chromium/components/device_signals/core/browser/mock_signals_collector.cc b/chromium/components/device_signals/core/browser/mock_signals_collector.cc index dd569e50e9f..e6b11d90e48 100644 --- a/chromium/components/device_signals/core/browser/mock_signals_collector.cc +++ b/chromium/components/device_signals/core/browser/mock_signals_collector.cc @@ -4,6 +4,8 @@ #include "components/device_signals/core/browser/mock_signals_collector.h" +#include "components/device_signals/core/browser/signals_types.h" + namespace device_signals { MockSignalsCollector::MockSignalsCollector() = default; diff --git a/chromium/components/device_signals/core/browser/mock_signals_collector.h b/chromium/components/device_signals/core/browser/mock_signals_collector.h index cb3e1172573..63db9597dbc 100644 --- a/chromium/components/device_signals/core/browser/mock_signals_collector.h +++ b/chromium/components/device_signals/core/browser/mock_signals_collector.h @@ -17,15 +17,17 @@ class MockSignalsCollector : public SignalsCollector { MockSignalsCollector(); ~MockSignalsCollector() override; - MOCK_METHOD(const std::unordered_set<std::string>, + MOCK_METHOD(bool, IsSignalSupported, (SignalName), (override)); + MOCK_METHOD(const std::unordered_set<SignalName>, GetSupportedSignalNames, (), (override)); MOCK_METHOD(void, GetSignal, - (const std::string&, - const base::Value&, - SignalsCollector::GetSignalCallback), + (SignalName signal_name, + const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure), (override)); }; diff --git a/chromium/components/device_signals/core/browser/mock_system_signals_service_host.h b/chromium/components/device_signals/core/browser/mock_system_signals_service_host.h index b5278eefcda..51aa57f77bf 100644 --- a/chromium/components/device_signals/core/browser/mock_system_signals_service_host.h +++ b/chromium/components/device_signals/core/browser/mock_system_signals_service_host.h @@ -26,9 +26,9 @@ class MockSystemSignalsService : public mojom::SystemSignalsService { ~MockSystemSignalsService() override; MOCK_METHOD(void, - GetBinarySignals, - (std::vector<device_signals::mojom::BinarySignalsRequestPtr>, - GetBinarySignalsCallback), + GetFileSystemSignals, + (const std::vector<device_signals::GetFileSystemInfoOptions>&, + GetFileSystemSignalsCallback), (override)); #if BUILDFLAG(IS_WIN) diff --git a/chromium/components/device_signals/core/browser/mock_user_delegate.h b/chromium/components/device_signals/core/browser/mock_user_delegate.h index c620f3aa8c2..ea1560d48b9 100644 --- a/chromium/components/device_signals/core/browser/mock_user_delegate.h +++ b/chromium/components/device_signals/core/browser/mock_user_delegate.h @@ -6,7 +6,6 @@ #define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_MOCK_USER_DELEGATE_H_ #include "components/device_signals/core/browser/user_delegate.h" -#include "components/signin/public/identity_manager/account_info.h" #include "testing/gmock/include/gmock/gmock.h" namespace device_signals { @@ -17,7 +16,8 @@ class MockUserDelegate : public UserDelegate { ~MockUserDelegate() override; MOCK_METHOD(bool, IsAffiliated, (), (const, override)); - MOCK_METHOD(bool, IsSameManagedUser, (const AccountInfo&), (const, override)); + MOCK_METHOD(bool, IsManaged, (), (const, override)); + MOCK_METHOD(bool, IsSameUser, (const std::string&), (const, override)); }; } // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/pref_names.cc b/chromium/components/device_signals/core/browser/pref_names.cc new file mode 100644 index 00000000000..d85093ee3e4 --- /dev/null +++ b/chromium/components/device_signals/core/browser/pref_names.cc @@ -0,0 +1,23 @@ +// Copyright 2022 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 "components/device_signals/core/browser/pref_names.h" + +#include "components/prefs/pref_registry_simple.h" + +namespace device_signals { +namespace prefs { + +// Whether or not admin requires managed users to share device signals when they +// sign in on an unmanaged device +const char kUnmanagedDeviceSignalsConsentFlowEnabled[] = + "device_signals.consent_collection_enabled"; + +} // namespace prefs + +void RegisterProfilePrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref( + prefs::kUnmanagedDeviceSignalsConsentFlowEnabled, false); +} +} // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/pref_names.h b/chromium/components/device_signals/core/browser/pref_names.h new file mode 100644 index 00000000000..ad1ffb22ab8 --- /dev/null +++ b/chromium/components/device_signals/core/browser/pref_names.h @@ -0,0 +1,19 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_PREF_NAMES_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_PREF_NAMES_H_ + +class PrefRegistrySimple; + +namespace device_signals { +namespace prefs { +extern const char kUnmanagedDeviceSignalsConsentFlowEnabled[]; +} // namespace prefs + +// Registers user preferences related to Device Signal Sharing. +void RegisterProfilePrefs(PrefRegistrySimple* registry); +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_PREF_NAMES_H_ diff --git a/chromium/components/device_signals/core/browser/signals_aggregator.h b/chromium/components/device_signals/core/browser/signals_aggregator.h index 6ff18d6ebad..2fe08085160 100644 --- a/chromium/components/device_signals/core/browser/signals_aggregator.h +++ b/chromium/components/device_signals/core/browser/signals_aggregator.h @@ -6,31 +6,27 @@ #define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_SIGNALS_AGGREGATOR_H_ #include "base/callback_forward.h" -#include "base/values.h" #include "components/keyed_service/core/keyed_service.h" namespace device_signals { -struct UserContext; +struct SignalsAggregationRequest; +struct SignalsAggregationResponse; class SignalsAggregator : public KeyedService { public: - using GetSignalsCallback = base::OnceCallback<void(base::Value)>; + using GetSignalsCallback = + base::OnceCallback<void(SignalsAggregationResponse)>; ~SignalsAggregator() override = default; - // Will asynchronously collect signals as defined in the `parameters` - // dictionary, where keys represent the names of the signals to be collected - // and values represent their collection parameters. Invokes `callback` with - // the collected signals stored in a dictionary, where the keys are the - // signal names and values are the collected values. If signal collection - // failed, the value returned in the callback may be solely an error string. - // Will use `user_context` to validate that the user has permissions to the - // device's signals. - // Currently only supports the collection of one signal (only one entry in - // `parameter`). - virtual void GetSignals(const UserContext& user_context, - base::Value::Dict parameters, + // Will asynchronously collect signals whose names are specified in the + // `request` object, and will also use its user context to validate that the + // user has permissions to the device's signals. Invokes `callback` with the + // collected signals. Currently only supports the collection of one signal + // (only one entry in `request.signal_names`) for simplicity as no current use + // case require supporting collecting multiple signals in one request. + virtual void GetSignals(const SignalsAggregationRequest& request, GetSignalsCallback callback) = 0; }; diff --git a/chromium/components/device_signals/core/browser/signals_aggregator_impl.cc b/chromium/components/device_signals/core/browser/signals_aggregator_impl.cc index f2cb8044188..9c4e3fa756d 100644 --- a/chromium/components/device_signals/core/browser/signals_aggregator_impl.cc +++ b/chromium/components/device_signals/core/browser/signals_aggregator_impl.cc @@ -1,15 +1,19 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "components/device_signals/core/browser/signals_aggregator_impl.h" +#include <functional> + #include "base/bind.h" #include "base/callback.h" #include "base/check.h" #include "base/strings/string_util.h" #include "base/values.h" +#include "components/device_signals/core/browser/metrics_utils.h" #include "components/device_signals/core/browser/signals_collector.h" +#include "components/device_signals/core/browser/signals_types.h" #include "components/device_signals/core/browser/user_context.h" #include "components/device_signals/core/browser/user_permission_service.h" #include "components/device_signals/core/common/signals_constants.h" @@ -18,21 +22,35 @@ namespace device_signals { namespace { -std::string PermissionToError(const UserPermission permission) { +SignalCollectionError PermissionToError(const UserPermission permission) { switch (permission) { case UserPermission::kUnaffiliated: - return errors::kUnaffiliatedUser; + return SignalCollectionError::kUnaffiliatedUser; case UserPermission::kMissingConsent: - return errors::kConsentRequired; + return SignalCollectionError::kConsentRequired; case UserPermission::kConsumerUser: case UserPermission::kUnknownUser: - return errors::kUnsupported; + case UserPermission::kMissingUser: + return SignalCollectionError::kInvalidUser; case UserPermission::kGranted: NOTREACHED(); - return ""; + return SignalCollectionError::kUnsupported; } } +void RespondWithError(SignalCollectionError error, + SignalsAggregator::GetSignalsCallback callback) { + SignalsAggregationResponse response; + response.top_level_error = error; + std::move(callback).Run(std::move(response)); +} + +void OnSignalRetrieved(std::unique_ptr<SignalsAggregationResponse> response, + SignalsAggregator::GetSignalsCallback callback) { + DCHECK(response); + std::move(callback).Run(std::move(*response)); +} + } // namespace SignalsAggregatorImpl::SignalsAggregatorImpl( @@ -45,62 +63,60 @@ SignalsAggregatorImpl::SignalsAggregatorImpl( SignalsAggregatorImpl::~SignalsAggregatorImpl() = default; -void SignalsAggregatorImpl::GetSignals(const UserContext& user_context, - base::Value::Dict parameters, +void SignalsAggregatorImpl::GetSignals(const SignalsAggregationRequest& request, GetSignalsCallback callback) { - if (parameters.empty()) { - std::move(callback).Run(base::Value(errors::kUnsupported)); + // Request for collection of multiple signals is not yet supported. Only the + // first signal will be returned. + if (request.signal_names.size() != 1) { + RespondWithError(SignalCollectionError::kUnsupported, std::move(callback)); return; } - // Request for collection of multiple signals is not yet supported. Only the - // first signal will be returned. - DCHECK(parameters.size() == 1); + LogSignalCollectionRequested(*request.signal_names.begin()); + // Capture a reference to `user_context` before calling the function to + // prevent a "use after move" warning on `request`, since we cannot guarantee + // the order of execution when evaluation function parameters. + const auto& user_context = request.user_context; permission_service_->CanCollectSignals( user_context, base::BindOnce(&SignalsAggregatorImpl::OnUserPermissionChecked, - weak_factory_.GetWeakPtr(), std::move(parameters), + weak_factory_.GetWeakPtr(), std::move(request), std::move(callback))); } void SignalsAggregatorImpl::OnUserPermissionChecked( - base::Value::Dict parameters, + const SignalsAggregationRequest& request, GetSignalsCallback callback, const UserPermission user_permission) { + LogUserPermissionChecked(user_permission); if (user_permission != UserPermission::kGranted) { - std::move(callback).Run(base::Value(PermissionToError(user_permission))); + RespondWithError(PermissionToError(user_permission), std::move(callback)); return; } - std::pair<const std::string&, const base::Value&> signal_request = - *parameters.begin(); + SignalName signal_name = *request.signal_names.begin(); for (const auto& collector : collectors_) { - const auto signals_set = collector->GetSupportedSignalNames(); - if (signals_set.find(signal_request.first) == signals_set.end()) { + if (!collector->IsSignalSupported(signal_name)) { // Signal is not supported by current collector. continue; } + // Create `response` on the heap to prevent memory access errors from + // occurring in the collectors. + auto response = std::make_unique<SignalsAggregationResponse>(); + SignalsAggregationResponse* response_ptr = response.get(); + // Signal is supported by current collector. - auto return_callback = base::BindOnce( - &SignalsAggregatorImpl::OnSignalCollected, weak_factory_.GetWeakPtr(), - signal_request.first, std::move(callback)); - collector->GetSignal(signal_request.first, signal_request.second, - std::move(return_callback)); + auto done_closure = base::BindOnce(&OnSignalRetrieved, std::move(response), + std::move(callback)); + collector->GetSignal(signal_name, request, *response_ptr, + std::move(done_closure)); return; } // Not a supported signal. - std::move(callback).Run(base::Value(errors::kUnsupported)); -} - -void SignalsAggregatorImpl::OnSignalCollected(const std::string signal_name, - GetSignalsCallback callback, - base::Value value) { - base::Value::Dict return_value; - return_value.Set(signal_name, std::move(value)); - std::move(callback).Run(base::Value(std::move(return_value))); + RespondWithError(SignalCollectionError::kUnsupported, std::move(callback)); } } // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/signals_aggregator_impl.h b/chromium/components/device_signals/core/browser/signals_aggregator_impl.h index 949b7cb920b..3d6b6b33549 100644 --- a/chromium/components/device_signals/core/browser/signals_aggregator_impl.h +++ b/chromium/components/device_signals/core/browser/signals_aggregator_impl.h @@ -29,19 +29,14 @@ class SignalsAggregatorImpl : public SignalsAggregator { ~SignalsAggregatorImpl() override; // SignalsAggregator: - void GetSignals(const UserContext& user_context, - base::Value::Dict parameters, + void GetSignals(const SignalsAggregationRequest& request, GetSignalsCallback callback) override; private: - void OnUserPermissionChecked(base::Value::Dict parameters, + void OnUserPermissionChecked(const SignalsAggregationRequest& request, GetSignalsCallback callback, const UserPermission user_permission); - void OnSignalCollected(const std::string signal_name, - GetSignalsCallback callback, - base::Value value); - base::raw_ptr<UserPermissionService> permission_service_; std::vector<std::unique_ptr<SignalsCollector>> collectors_; diff --git a/chromium/components/device_signals/core/browser/signals_aggregator_impl_unittest.cc b/chromium/components/device_signals/core/browser/signals_aggregator_impl_unittest.cc index 42d9c3c5d97..45065984dc5 100644 --- a/chromium/components/device_signals/core/browser/signals_aggregator_impl_unittest.cc +++ b/chromium/components/device_signals/core/browser/signals_aggregator_impl_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. @@ -9,10 +9,12 @@ #include <vector> #include "base/memory/raw_ptr.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/task_environment.h" #include "base/test/test_future.h" #include "components/device_signals/core/browser/mock_signals_collector.h" #include "components/device_signals/core/browser/mock_user_permission_service.h" +#include "components/device_signals/core/browser/signals_types.h" #include "components/device_signals/core/browser/user_context.h" #include "components/device_signals/core/browser/user_permission_service.h" #include "components/device_signals/core/common/signals_constants.h" @@ -29,79 +31,22 @@ namespace device_signals { namespace { -constexpr char kFakeSignalName[] = "signal_name"; -constexpr char kOtherFakeSignalName[] = "other_signal_name"; - constexpr char kGaiaId[] = "gaia-id"; -base::Value GetFakeSignalParameter() { - return base::Value("some parameter needed by the signal collector"); -} - -base::Value GetFakeSignalValue() { - base::Value::Dict fake_signal_value; - fake_signal_value.Set("some_key", "some_value"); - return base::Value(std::move(fake_signal_value)); -} - -base::Value GetOtherFakeSignalParameter() { - base::Value::Dict fake_signal_value; - fake_signal_value.Set("some_file_path", "some/path"); - return base::Value(std::move(fake_signal_value)); -} - -base::Value GetOtherFakeSignalValue() { - return base::Value("just a string"); -} - -std::unique_ptr<MockSignalsCollector> GetCollectorForFakeSignal() { - auto mock_collector = std::make_unique<MockSignalsCollector>(); - ON_CALL(*mock_collector.get(), GetSupportedSignalNames()) - .WillByDefault( - Return(std::unordered_set<std::string>({kFakeSignalName}))); - - ON_CALL(*mock_collector.get(), GetSignal(kFakeSignalName, _, _)) - .WillByDefault( - Invoke([](const std::string& signal_name, const base::Value& params, - SignalsCollector::GetSignalCallback callback) { - EXPECT_EQ(params, GetFakeSignalParameter()); - std::move(callback).Run(GetFakeSignalValue()); - })); - - return mock_collector; -} - -std::unique_ptr<MockSignalsCollector> GetCollectorForOtherFakeSignal() { - auto mock_collector = std::make_unique<MockSignalsCollector>(); - ON_CALL(*mock_collector.get(), GetSupportedSignalNames()) - .WillByDefault( - Return(std::unordered_set<std::string>({kOtherFakeSignalName}))); - - ON_CALL(*mock_collector.get(), GetSignal(kOtherFakeSignalName, _, _)) - .WillByDefault( - Invoke([](const std::string& signal_name, const base::Value& params, - SignalsCollector::GetSignalCallback callback) { - EXPECT_EQ(params, GetOtherFakeSignalParameter()); - std::move(callback).Run(GetOtherFakeSignalValue()); - })); - - return mock_collector; -} - } // namespace class SignalsAggregatorImplTest : public testing::Test { protected: SignalsAggregatorImplTest() { - auto fake_signal_collector = GetCollectorForFakeSignal(); - fake_signal_collector_ = fake_signal_collector.get(); + auto av_signal_collector = GetFakeCollector(SignalName::kAntiVirus); + av_signal_collector_ = av_signal_collector.get(); - auto other_fake_signal_collector = GetCollectorForOtherFakeSignal(); - other_fake_signal_collector_ = other_fake_signal_collector.get(); + auto hotfix_signal_collector = GetFakeCollector(SignalName::kHotfixes); + hotfix_signal_collector_ = hotfix_signal_collector.get(); std::vector<std::unique_ptr<SignalsCollector>> collectors; - collectors.push_back(std::move(fake_signal_collector)); - collectors.push_back(std::move(other_fake_signal_collector)); + collectors.push_back(std::move(av_signal_collector)); + collectors.push_back(std::move(hotfix_signal_collector)); aggregator_ = std::make_unique<SignalsAggregatorImpl>( &mock_permission_service_, std::move(collectors)); } @@ -114,72 +59,98 @@ class SignalsAggregatorImplTest : public testing::Test { }); } + SignalsAggregationRequest CreateRequest() { + SignalsAggregationRequest request; + request.user_context = user_context_; + return request; + } + + std::unique_ptr<MockSignalsCollector> GetFakeCollector( + SignalName signal_name) { + auto mock_collector = std::make_unique<MockSignalsCollector>(); + ON_CALL(*mock_collector.get(), IsSignalSupported(_)) + .WillByDefault(Return(false)); + ON_CALL(*mock_collector.get(), IsSignalSupported(signal_name)) + .WillByDefault(Return(true)); + + ON_CALL(*mock_collector.get(), GetSupportedSignalNames()) + .WillByDefault(Return(std::unordered_set<SignalName>({signal_name}))); + + ON_CALL(*mock_collector.get(), GetSignal(signal_name, _, _, _)) + .WillByDefault(Invoke([&](SignalName signal_name, + const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure) { + std::move(done_closure).Run(); + })); + + return mock_collector; + } + base::test::TaskEnvironment task_environment_; - raw_ptr<MockSignalsCollector> fake_signal_collector_; - raw_ptr<MockSignalsCollector> other_fake_signal_collector_; + raw_ptr<MockSignalsCollector> av_signal_collector_; + raw_ptr<MockSignalsCollector> hotfix_signal_collector_; testing::StrictMock<MockUserPermissionService> mock_permission_service_; UserContext user_context_{kGaiaId}; std::unique_ptr<SignalsAggregatorImpl> aggregator_; + base::HistogramTester histogram_tester_; }; // Tests that the aggregator will return an empty value when given an empty // parameter dictionary. TEST_F(SignalsAggregatorImplTest, GetSignals_NoSignal) { - base::test::TestFuture<base::Value> future; - base::Value::Dict empty_value; - aggregator_->GetSignals(user_context_, std::move(empty_value), - future.GetCallback()); - EXPECT_EQ(future.Get(), base::Value(errors::kUnsupported)); -} - -// Tests how the aggregator behaves when given a parameter with a single signal -// which is supported by one of the collectors. -TEST_F(SignalsAggregatorImplTest, GetSignals_SingleSignal_Supported) { - GrantUserPermission(); - - base::Value::Dict parameters; - parameters.Set(kFakeSignalName, GetFakeSignalParameter()); - - EXPECT_CALL(*fake_signal_collector_, GetSupportedSignalNames()).Times(1); - EXPECT_CALL(*fake_signal_collector_, GetSignal(kFakeSignalName, _, _)) - .Times(1); + base::test::TestFuture<SignalsAggregationResponse> future; + aggregator_->GetSignals(std::move(CreateRequest()), future.GetCallback()); - EXPECT_CALL(*other_fake_signal_collector_, GetSignal(_, _, _)).Times(0); + SignalsAggregationResponse response = future.Get(); + ASSERT_TRUE(response.top_level_error.has_value()); + EXPECT_EQ(response.top_level_error.value(), + SignalCollectionError::kUnsupported); +} - base::test::TestFuture<base::Value> future; - aggregator_->GetSignals(user_context_, std::move(parameters), - future.GetCallback()); +// Tests that the aggregator will return an empty value when given a request +// with multiple signal names. +TEST_F(SignalsAggregatorImplTest, GetSignals_MultipleSignals) { + auto request = CreateRequest(); + request.signal_names.emplace(SignalName::kAntiVirus); + request.signal_names.emplace(SignalName::kHotfixes); - base::Value::Dict expected_value; - expected_value.Set(kFakeSignalName, GetFakeSignalValue()); + base::test::TestFuture<SignalsAggregationResponse> future; + aggregator_->GetSignals(request, future.GetCallback()); - EXPECT_EQ(future.Get(), base::Value(std::move(expected_value))); + SignalsAggregationResponse response = future.Get(); + ASSERT_TRUE(response.top_level_error.has_value()); + EXPECT_EQ(response.top_level_error.value(), + SignalCollectionError::kUnsupported); } // Tests how the aggregator behaves when given a parameter with a single signal -// which is supported by another collector. -TEST_F(SignalsAggregatorImplTest, GetSignals_SingleSignal_SupportedOther) { +// which is supported by one of the collectors. +TEST_F(SignalsAggregatorImplTest, GetSignals_SingleSignal_Supported) { GrantUserPermission(); - base::Value::Dict parameters; - parameters.Set(kOtherFakeSignalName, GetOtherFakeSignalParameter()); + auto expected_signal_name = SignalName::kAntiVirus; + auto request = CreateRequest(); + request.signal_names.emplace(expected_signal_name); - EXPECT_CALL(*other_fake_signal_collector_, GetSupportedSignalNames()) + EXPECT_CALL(*av_signal_collector_, IsSignalSupported(expected_signal_name)) .Times(1); - EXPECT_CALL(*other_fake_signal_collector_, - GetSignal(kOtherFakeSignalName, _, _)) + EXPECT_CALL(*av_signal_collector_, + GetSignal(SignalName::kAntiVirus, request, _, _)) .Times(1); - EXPECT_CALL(*fake_signal_collector_, GetSignal(_, _, _)).Times(0); + EXPECT_CALL(*hotfix_signal_collector_, GetSignal(_, _, _, _)).Times(0); - base::test::TestFuture<base::Value> future; - aggregator_->GetSignals(user_context_, std::move(parameters), - future.GetCallback()); + base::test::TestFuture<SignalsAggregationResponse> future; + aggregator_->GetSignals(std::move(request), future.GetCallback()); - base::Value::Dict expected_value; - expected_value.Set(kOtherFakeSignalName, GetOtherFakeSignalValue()); + SignalsAggregationResponse response = future.Get(); + EXPECT_FALSE(response.top_level_error.has_value()); - EXPECT_EQ(future.Get(), base::Value(std::move(expected_value))); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Request", expected_signal_name, 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.UserPermission", UserPermission::kGranted, 1); } // Tests how the aggregator behaves when given a parameter with a single signal @@ -187,25 +158,39 @@ TEST_F(SignalsAggregatorImplTest, GetSignals_SingleSignal_SupportedOther) { TEST_F(SignalsAggregatorImplTest, GetSignals_SingleSignal_Unsupported) { GrantUserPermission(); - base::Value::Dict parameters; - parameters.Set("something unsupported", base::Value()); + auto expected_signal_name = SignalName::kFileSystemInfo; + auto request = CreateRequest(); + request.signal_names.emplace(expected_signal_name); + + base::test::TestFuture<SignalsAggregationResponse> future; + aggregator_->GetSignals(std::move(request), future.GetCallback()); - base::test::TestFuture<base::Value> future; - aggregator_->GetSignals(user_context_, std::move(parameters), - future.GetCallback()); - EXPECT_EQ(future.Get(), base::Value(errors::kUnsupported)); + SignalsAggregationResponse response = future.Get(); + ASSERT_TRUE(response.top_level_error.has_value()); + EXPECT_EQ(response.top_level_error.value(), + SignalCollectionError::kUnsupported); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Request", expected_signal_name, 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.UserPermission", UserPermission::kGranted, 1); } // Tests how the aggregator behaves when encountering user permission errors. TEST_F(SignalsAggregatorImplTest, GetSignals_InvalidUserPermissions) { - std::map<UserPermission, std::string> permission_to_error_map; + std::map<UserPermission, SignalCollectionError> permission_to_error_map; permission_to_error_map[UserPermission::kUnaffiliated] = - errors::kUnaffiliatedUser; + SignalCollectionError::kUnaffiliatedUser; permission_to_error_map[UserPermission::kMissingConsent] = - errors::kConsentRequired; - permission_to_error_map[UserPermission::kConsumerUser] = errors::kUnsupported; - permission_to_error_map[UserPermission::kUnknownUser] = errors::kUnsupported; - + SignalCollectionError::kConsentRequired; + permission_to_error_map[UserPermission::kConsumerUser] = + SignalCollectionError::kInvalidUser; + permission_to_error_map[UserPermission::kUnknownUser] = + SignalCollectionError::kInvalidUser; + permission_to_error_map[UserPermission::kMissingUser] = + SignalCollectionError::kInvalidUser; + + uint16_t item_index = 0; for (const auto& test_case : permission_to_error_map) { EXPECT_CALL(mock_permission_service_, CanCollectSignals(user_context_, _)) .WillOnce( @@ -215,14 +200,21 @@ TEST_F(SignalsAggregatorImplTest, GetSignals_InvalidUserPermissions) { }); // This value is not important for these test cases. - base::Value::Dict parameters; - parameters.Set("something unsupported", base::Value()); - - base::test::TestFuture<base::Value> future; - aggregator_->GetSignals(user_context_, std::move(parameters), - future.GetCallback()); - - EXPECT_EQ(future.Get(), base::Value(test_case.second)); + auto expected_signal_name = SignalName::kAntiVirus; + auto request = CreateRequest(); + request.signal_names.emplace(expected_signal_name); + + base::test::TestFuture<SignalsAggregationResponse> future; + aggregator_->GetSignals(std::move(request), future.GetCallback()); + SignalsAggregationResponse response = future.Get(); + ASSERT_TRUE(response.top_level_error.has_value()); + EXPECT_EQ(response.top_level_error.value(), test_case.second); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Request", expected_signal_name, + ++item_index); + histogram_tester_.ExpectBucketCount( + "Enterprise.DeviceSignals.UserPermission", test_case.first, 1); } } diff --git a/chromium/components/device_signals/core/browser/signals_collector.h b/chromium/components/device_signals/core/browser/signals_collector.h index 9c63ae5a151..95712a2e0ee 100644 --- a/chromium/components/device_signals/core/browser/signals_collector.h +++ b/chromium/components/device_signals/core/browser/signals_collector.h @@ -5,31 +5,36 @@ #ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_SIGNALS_COLLECTOR_H_ #define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_SIGNALS_COLLECTOR_H_ -#include <string> #include <unordered_set> #include "base/callback_forward.h" -namespace base { -class Value; -} // namespace base - namespace device_signals { +enum class SignalName; +struct SignalsAggregationRequest; +struct SignalsAggregationResponse; + class SignalsCollector { public: - using GetSignalCallback = base::OnceCallback<void(base::Value)>; - virtual ~SignalsCollector() = default; - // Returns the set of signal names that this collector can collect. - virtual const std::unordered_set<std::string> GetSupportedSignalNames() = 0; + // Returns true if `signal_name` is part of the set of signals supported by + // this collector. + virtual bool IsSignalSupported(SignalName signal_name) = 0; - // Collects the signal named `signal_name` using `params` (if needed), and - // invokes `callback` with the signal value. - virtual void GetSignal(const std::string& signal_name, - const base::Value& params, - GetSignalCallback callback) = 0; + // Returns the set of signal names that this collector can collect. + virtual const std::unordered_set<SignalName> GetSupportedSignalNames() = 0; + + // Collects the signal named `signal_name` using parameters in `request` + // (if needed), sets the collected values on `response` and invokes + // `done_closure` when the signal is collected. `response` is owned by the + // caller who is responsible for keeping the value alive while the signal is + // being collected. + virtual void GetSignal(SignalName signal_name, + const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure) = 0; }; } // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/signals_types.cc b/chromium/components/device_signals/core/browser/signals_types.cc new file mode 100644 index 00000000000..c92f4f8943f --- /dev/null +++ b/chromium/components/device_signals/core/browser/signals_types.cc @@ -0,0 +1,85 @@ +// Copyright 2022 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 "components/device_signals/core/browser/signals_types.h" + +#include "components/device_signals/core/common/signals_constants.h" + +namespace device_signals { + +const std::string ErrorToString(SignalCollectionError error) { + switch (error) { + case SignalCollectionError::kConsentRequired: + return errors::kConsentRequired; + case SignalCollectionError::kUnaffiliatedUser: + return errors::kUnaffiliatedUser; + case SignalCollectionError::kUnsupported: + return errors::kUnsupported; + case SignalCollectionError::kMissingSystemService: + return errors::kMissingSystemService; + case SignalCollectionError::kMissingBundle: + return errors::kMissingBundle; + case SignalCollectionError::kInvalidUser: + return errors::kInvalidUser; + case SignalCollectionError::kMissingParameters: + return errors::kMissingParameters; + } +} + +BaseSignalResponse::~BaseSignalResponse() = default; + +#if BUILDFLAG(IS_WIN) +AntiVirusSignalResponse::AntiVirusSignalResponse() = default; +AntiVirusSignalResponse::AntiVirusSignalResponse( + const AntiVirusSignalResponse&) = default; + +AntiVirusSignalResponse& AntiVirusSignalResponse::operator=( + const AntiVirusSignalResponse&) = default; + +AntiVirusSignalResponse::~AntiVirusSignalResponse() = default; + +HotfixSignalResponse::HotfixSignalResponse() = default; +HotfixSignalResponse::HotfixSignalResponse(const HotfixSignalResponse&) = + default; + +HotfixSignalResponse& HotfixSignalResponse::operator=( + const HotfixSignalResponse&) = default; + +HotfixSignalResponse::~HotfixSignalResponse() = default; +#endif // BUILDFLAG(IS_WIN) + +FileSystemInfoResponse::FileSystemInfoResponse() = default; +FileSystemInfoResponse::FileSystemInfoResponse(const FileSystemInfoResponse&) = + default; + +FileSystemInfoResponse& FileSystemInfoResponse::operator=( + const FileSystemInfoResponse&) = default; + +FileSystemInfoResponse::~FileSystemInfoResponse() = default; + +SignalsAggregationRequest::SignalsAggregationRequest() = default; +SignalsAggregationRequest::SignalsAggregationRequest( + const SignalsAggregationRequest&) = default; + +SignalsAggregationRequest& SignalsAggregationRequest::operator=( + const SignalsAggregationRequest&) = default; + +SignalsAggregationRequest::~SignalsAggregationRequest() = default; + +bool SignalsAggregationRequest::operator==( + const SignalsAggregationRequest& other) const { + return user_context == other.user_context && + signal_names == other.signal_names; +} + +SignalsAggregationResponse::SignalsAggregationResponse() = default; +SignalsAggregationResponse::SignalsAggregationResponse( + const SignalsAggregationResponse&) = default; + +SignalsAggregationResponse& SignalsAggregationResponse::operator=( + const SignalsAggregationResponse&) = default; + +SignalsAggregationResponse::~SignalsAggregationResponse() = default; + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/signals_types.h b/chromium/components/device_signals/core/browser/signals_types.h new file mode 100644 index 00000000000..ae7792f4e6e --- /dev/null +++ b/chromium/components/device_signals/core/browser/signals_types.h @@ -0,0 +1,150 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_SIGNALS_TYPES_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_SIGNALS_TYPES_H_ + +#include <unordered_set> +#include <vector> + +#include "build/build_config.h" +#include "components/device_signals/core/browser/user_context.h" +#include "components/device_signals/core/common/common_types.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +#if BUILDFLAG(IS_WIN) +#include "components/device_signals/core/common/win/win_types.h" +#endif // BUILDFLAG(IS_WIN) + +namespace device_signals { + +// Enum of names representing signals bundles that can be aggregated via the +// SignalsAggregator. +// These values are persisted to logs and should not be renumbered. Please +// update the DeviceSignalsSignalName enum in enums.xml when adding a new +// value here. +enum class SignalName { + kAntiVirus, + kHotfixes, + kFileSystemInfo, + kSystemSettings, + kMaxValue = kSystemSettings +}; + +// Superset of all signal collection errors that can occur, including top-level +// as well as per-bundle errors. +// These values are persisted to logs and should not be renumbered. Please +// update the DeviceSignalsSignalCollectionError enum in enums.xml when adding a +// new value here. +enum class SignalCollectionError { + kConsentRequired, + kUnaffiliatedUser, + kUnsupported, + kMissingSystemService, + kMissingBundle, + kInvalidUser, + kMissingParameters, + kMaxValue = kMissingParameters +}; + +const std::string ErrorToString(SignalCollectionError error); + +// Base struct type that each specific signal bundle types should extend. The +// derived signal bundles/responses should group a set of signals that +// cohesively belong together (e.g. device-level signals, policy values +// signals). +struct BaseSignalResponse { + virtual ~BaseSignalResponse(); + + // If set, represents a collection error that occurred while getting the + // signal. + absl::optional<SignalCollectionError> collection_error = absl::nullopt; +}; + +#if BUILDFLAG(IS_WIN) +struct AntiVirusSignalResponse : BaseSignalResponse { + AntiVirusSignalResponse(); + + AntiVirusSignalResponse(const AntiVirusSignalResponse&); + AntiVirusSignalResponse& operator=(const AntiVirusSignalResponse&); + + ~AntiVirusSignalResponse() override; + + std::vector<AvProduct> av_products{}; +}; + +struct HotfixSignalResponse : BaseSignalResponse { + HotfixSignalResponse(); + + HotfixSignalResponse(const HotfixSignalResponse&); + HotfixSignalResponse& operator=(const HotfixSignalResponse&); + + ~HotfixSignalResponse() override; + + std::vector<InstalledHotfix> hotfixes{}; +}; +#endif // BUILDFLAG(IS_WIN) + +struct FileSystemInfoResponse : BaseSignalResponse { + FileSystemInfoResponse(); + + FileSystemInfoResponse(const FileSystemInfoResponse&); + FileSystemInfoResponse& operator=(const FileSystemInfoResponse&); + + ~FileSystemInfoResponse() override; + + std::vector<FileSystemItem> file_system_items{}; +}; + +// Request struct containing properties that will be used by the +// SignalAggregator to validate signals access permissions while delegating +// the collection to the right Collectors. Signals that require parameters (e.g. +// FileSystemInfo) will look for them in this object. +struct SignalsAggregationRequest { + SignalsAggregationRequest(); + + SignalsAggregationRequest(const SignalsAggregationRequest&); + SignalsAggregationRequest& operator=(const SignalsAggregationRequest&); + + ~SignalsAggregationRequest(); + + // Information about the user for whom these signals are collected. + UserContext user_context{}; + + // Names of the signals that need to be collected. + std::unordered_set<SignalName> signal_names{}; + + // Parameters required when requesting the collection of signals living on + // the device's file system. + std::vector<GetFileSystemInfoOptions> file_system_signal_parameters{}; + + bool operator==(const SignalsAggregationRequest& other) const; +}; + +// Response from a signal collection request sent through the SignalsAggregator. +// The signal bundles on this object will be set according to the set of signal +// names given in the corresponding `SignalsAggregationRequest`. +struct SignalsAggregationResponse { + SignalsAggregationResponse(); + + SignalsAggregationResponse(const SignalsAggregationResponse&); + SignalsAggregationResponse& operator=(const SignalsAggregationResponse&); + + ~SignalsAggregationResponse(); + + // If set, represents an error that occurred before any signal could be + // collected. + absl::optional<SignalCollectionError> top_level_error = absl::nullopt; + +#if BUILDFLAG(IS_WIN) + absl::optional<AntiVirusSignalResponse> av_signal_response = absl::nullopt; + absl::optional<HotfixSignalResponse> hotfix_signal_response = absl::nullopt; +#endif // BUILDFLAG(IS_WIN) + absl::optional<FileSystemInfoResponse> file_system_info_response = + absl::nullopt; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_SIGNALS_TYPES_H_ diff --git a/chromium/components/device_signals/core/browser/user_context.cc b/chromium/components/device_signals/core/browser/user_context.cc index ed3d091b247..8a74ea46842 100644 --- a/chromium/components/device_signals/core/browser/user_context.cc +++ b/chromium/components/device_signals/core/browser/user_context.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. diff --git a/chromium/components/device_signals/core/browser/user_delegate.h b/chromium/components/device_signals/core/browser/user_delegate.h index a98f430be15..bfe1c554536 100644 --- a/chromium/components/device_signals/core/browser/user_delegate.h +++ b/chromium/components/device_signals/core/browser/user_delegate.h @@ -5,23 +5,26 @@ #ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_USER_DELEGATE_H_ #define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_USER_DELEGATE_H_ -struct AccountInfo; +#include <string> namespace device_signals { -// Delegate representing the user that is currently logged into the browser +// Delegate representing the user that is currently logged-in to the browser // itself. class UserDelegate { public: virtual ~UserDelegate() = default; - // Returns true if the current user is managed by an organization that is - // affiliated with the organization managing the device. + // Returns true if the current browser user is managed by an organization that + // is affiliated with the organization managing the device. virtual bool IsAffiliated() const = 0; - // Returns true if the user currently logged into the browser is managed and - // is the same user as `user`. - virtual bool IsSameManagedUser(const AccountInfo& user) const = 0; + // Returns true if the current browser user is managed. + virtual bool IsManaged() const = 0; + + // Returns true if `gaia_id` represents the same user as the one currently + // logged-in to the browser. + virtual bool IsSameUser(const std::string& gaia_id) const = 0; }; } // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/user_permission_service.h b/chromium/components/device_signals/core/browser/user_permission_service.h index e82c95cf34a..2db08b4c77a 100644 --- a/chromium/components/device_signals/core/browser/user_permission_service.h +++ b/chromium/components/device_signals/core/browser/user_permission_service.h @@ -12,6 +12,10 @@ namespace device_signals { struct UserContext; +// Contains possible outcomes of a signals collection permission check. +// These values are persisted to logs and should not be renumbered. Please +// update the DeviceSignalsUserPermission enum in enums.xml when adding a new +// value here. enum class UserPermission { // Returned when the user is part of an organization that is not affiliated // with the organization currently managing the browser. @@ -24,12 +28,17 @@ enum class UserPermission { // Returned when the user is not part of any organization. kConsumerUser = 2, - // Returned when the given user context does not represent any currently - // logged-in user. + // Returned when the given user context does not represent the current browser + // user (e.g. Profile user). kUnknownUser = 3, + // Returned when the no user information was given. + kMissingUser = 4, + // Returned when the user is granted permission to the device's signals. - kGranted = 4, + kGranted = 5, + + kMaxValue = kGranted }; // Service that can be used to conduct permission checks on given users. The diff --git a/chromium/components/device_signals/core/browser/user_permission_service_impl.cc b/chromium/components/device_signals/core/browser/user_permission_service_impl.cc index c2ac146f9ff..9e18c292e15 100644 --- a/chromium/components/device_signals/core/browser/user_permission_service_impl.cc +++ b/chromium/components/device_signals/core/browser/user_permission_service_impl.cc @@ -8,18 +8,14 @@ #include "components/device_signals/core/browser/user_context.h" #include "components/device_signals/core/browser/user_delegate.h" #include "components/policy/core/common/management/management_service.h" -#include "components/signin/public/identity_manager/identity_manager.h" namespace device_signals { UserPermissionServiceImpl::UserPermissionServiceImpl( - signin::IdentityManager* identity_manager, policy::ManagementService* management_service, std::unique_ptr<UserDelegate> user_delegate) - : identity_manager_(identity_manager), - management_service_(management_service), + : management_service_(management_service), user_delegate_(std::move(user_delegate)) { - DCHECK(identity_manager_); DCHECK(management_service_); DCHECK(user_delegate_); } @@ -29,24 +25,18 @@ UserPermissionServiceImpl::~UserPermissionServiceImpl() = default; void UserPermissionServiceImpl::CanCollectSignals( const UserContext& user_context, UserPermissionService::CanCollectCallback callback) { - // Return "unknown user" if the user ID is invalid, or does not represent a - // logged-in user. + // Return "unknown user" if no user ID was given. if (user_context.user_id.empty()) { - std::move(callback).Run(UserPermission::kUnknownUser); + std::move(callback).Run(UserPermission::kMissingUser); return; } - // TODO(b:233250828): Verify this function covers the required use cases. - auto account_info = - identity_manager_->FindExtendedAccountInfoByGaiaId(user_context.user_id); - - if (account_info.IsEmpty()) { + if (!user_delegate_->IsSameUser(user_context.user_id)) { std::move(callback).Run(UserPermission::kUnknownUser); return; } - // Return "consumer user" if the user is not managed by an organization. - if (!account_info.IsManaged()) { + if (!user_delegate_->IsManaged()) { std::move(callback).Run(UserPermission::kConsumerUser); return; } @@ -61,20 +51,16 @@ void UserPermissionServiceImpl::CanCollectSignals( return; } - // If the account info represents the current browser user (e.g. Profile - // user), verify if that user is part of an affiliated organization. - if (user_delegate_->IsSameManagedUser(account_info)) { - UserPermission permission = user_delegate_->IsAffiliated() - ? UserPermission::kGranted - : UserPermission::kUnaffiliated; - std::move(callback).Run(permission); + if (!user_delegate_->IsAffiliated()) { + std::move(callback).Run(UserPermission::kUnaffiliated); return; } - // TODO(b:232269863): Fetch customer IDs for that user by calling the DM - // server, then cache results here (and clear cache when the account is - // logged-out). - std::move(callback).Run(UserPermission::kUnaffiliated); + // At this point, the given user is: + // - The same user as the browser user, + // - Is managed by an org affiliated with the org managing the browser. + // They are, therefore, allowed to collect signals. + std::move(callback).Run(UserPermission::kGranted); } } // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/user_permission_service_impl.h b/chromium/components/device_signals/core/browser/user_permission_service_impl.h index 3e77b195987..aa29d32fef0 100644 --- a/chromium/components/device_signals/core/browser/user_permission_service_impl.h +++ b/chromium/components/device_signals/core/browser/user_permission_service_impl.h @@ -15,18 +15,13 @@ namespace policy { class ManagementService; } // namespace policy -namespace signin { -class IdentityManager; -} // namespace signin - namespace device_signals { class UserDelegate; class UserPermissionServiceImpl : public UserPermissionService { public: - UserPermissionServiceImpl(signin::IdentityManager* identity_manager, - policy::ManagementService* management_service, + UserPermissionServiceImpl(policy::ManagementService* management_service, std::unique_ptr<UserDelegate> user_delegate); UserPermissionServiceImpl(const UserPermissionServiceImpl&) = delete; @@ -40,7 +35,6 @@ class UserPermissionServiceImpl : public UserPermissionService { CanCollectCallback callback) override; private: - base::raw_ptr<signin::IdentityManager> identity_manager_; base::raw_ptr<policy::ManagementService> management_service_; std::unique_ptr<UserDelegate> user_delegate_; diff --git a/chromium/components/device_signals/core/browser/user_permission_service_impl_unittest.cc b/chromium/components/device_signals/core/browser/user_permission_service_impl_unittest.cc index d6579051c74..f70cadc6f93 100644 --- a/chromium/components/device_signals/core/browser/user_permission_service_impl_unittest.cc +++ b/chromium/components/device_signals/core/browser/user_permission_service_impl_unittest.cc @@ -12,8 +12,6 @@ #include "components/device_signals/core/browser/user_delegate.h" #include "components/policy/core/common/management/management_service.h" #include "components/policy/core/common/management/scoped_management_service_override_for_testing.h" -#include "components/signin/public/identity_manager/account_info.h" -#include "components/signin/public/identity_manager/identity_test_environment.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -27,8 +25,6 @@ namespace device_signals { namespace { constexpr char kUserGaiaId[] = "some-gaia-id"; -constexpr char kUserEmail[] = "user@abc.com"; -constexpr char kHostedDomain[] = "example.com"; class TestManagementService : public policy::ManagementService { public: @@ -55,13 +51,11 @@ class UserPermissionServiceImplTest : public testing::Test { mock_user_delegate_ = mock_user_delegate.get(); permission_service_ = std::make_unique<UserPermissionServiceImpl>( - identity_test_env_.identity_manager(), &management_service_, - std::move(mock_user_delegate)); + &management_service_, std::move(mock_user_delegate)); } base::test::TaskEnvironment task_environment_; - signin::IdentityTestEnvironment identity_test_env_; TestManagementService management_service_; ScopedManagementServiceOverrideForTesting scoped_override_; raw_ptr<testing::StrictMock<MockUserDelegate>> mock_user_delegate_; @@ -74,32 +68,35 @@ TEST_F(UserPermissionServiceImplTest, CanCollectSignals_EmptyUserId) { base::test::TestFuture<UserPermission> future; UserContext user_context; permission_service_->CanCollectSignals(user_context, future.GetCallback()); - EXPECT_EQ(future.Get(), UserPermission::kUnknownUser); + EXPECT_EQ(future.Get(), UserPermission::kMissingUser); } -// Tests CanCollectSignals with a user ID that does not represent a valid user. -TEST_F(UserPermissionServiceImplTest, CanCollectSignals_UserId_NoUser) { - base::test::TestFuture<UserPermission> future; +// Tests CanCollectSignals with a user ID that does not represent the current +// browser user. +TEST_F(UserPermissionServiceImplTest, CanCollectSignals_UserId_NotSameUser) { UserContext user_context; user_context.user_id = kUserGaiaId; + + // Mock that it is not the same user. + EXPECT_CALL(*mock_user_delegate_, IsSameUser(kUserGaiaId)) + .WillOnce(Return(false)); + + base::test::TestFuture<UserPermission> future; permission_service_->CanCollectSignals(user_context, future.GetCallback()); EXPECT_EQ(future.Get(), UserPermission::kUnknownUser); } -// Tests CanCollectSignals with a user ID that does not represent a managed -// user. +// Tests CanCollectSignals with a user ID that represents the browser user, but +// that user is not managed. TEST_F(UserPermissionServiceImplTest, CanCollectSignals_User_NotManaged) { - // Create known account. - AccountInfo account = identity_test_env_.MakeAccountAvailableWithCookies( - kUserEmail, kUserGaiaId); + UserContext user_context; + user_context.user_id = kUserGaiaId; - // Make sure there is no hosted domain. - account.hosted_domain = ""; - identity_test_env_.UpdateAccountInfoForAccount(account); + EXPECT_CALL(*mock_user_delegate_, IsSameUser(kUserGaiaId)) + .WillOnce(Return(true)); + EXPECT_CALL(*mock_user_delegate_, IsManaged()).WillOnce(Return(false)); base::test::TestFuture<UserPermission> future; - UserContext user_context; - user_context.user_id = account.gaia; permission_service_->CanCollectSignals(user_context, future.GetCallback()); EXPECT_EQ(future.Get(), UserPermission::kConsumerUser); } @@ -111,17 +108,14 @@ TEST_F(UserPermissionServiceImplTest, CanCollectSignals_BrowserNotManaged) { ScopedManagementServiceOverrideForTesting another_scope( &management_service_, EnterpriseManagementAuthority::CLOUD); - // Create known account. - AccountInfo account = identity_test_env_.MakeAccountAvailableWithCookies( - kUserEmail, kUserGaiaId); + UserContext user_context; + user_context.user_id = kUserGaiaId; - // Make sure there is a hosted domain. - account.hosted_domain = kHostedDomain; - identity_test_env_.UpdateAccountInfoForAccount(account); + EXPECT_CALL(*mock_user_delegate_, IsSameUser(kUserGaiaId)) + .WillOnce(Return(true)); + EXPECT_CALL(*mock_user_delegate_, IsManaged()).WillOnce(Return(true)); base::test::TestFuture<UserPermission> future; - UserContext user_context; - user_context.user_id = account.gaia; permission_service_->CanCollectSignals(user_context, future.GetCallback()); EXPECT_EQ(future.Get(), UserPermission::kMissingConsent); } @@ -131,21 +125,15 @@ TEST_F(UserPermissionServiceImplTest, CanCollectSignals_BrowserNotManaged) { // the browser's org. TEST_F(UserPermissionServiceImplTest, CanCollectSignals_BrowserManaged_ProfileUser_Unaffiliated) { - // Create known account. - AccountInfo account = identity_test_env_.MakeAccountAvailableWithCookies( - kUserEmail, kUserGaiaId); - - // Make sure there is a hosted domain. - account.hosted_domain = kHostedDomain; - identity_test_env_.UpdateAccountInfoForAccount(account); + UserContext user_context; + user_context.user_id = kUserGaiaId; - EXPECT_CALL(*mock_user_delegate_, IsSameManagedUser(account)) + EXPECT_CALL(*mock_user_delegate_, IsSameUser(kUserGaiaId)) .WillOnce(Return(true)); + EXPECT_CALL(*mock_user_delegate_, IsManaged()).WillOnce(Return(true)); EXPECT_CALL(*mock_user_delegate_, IsAffiliated()).WillOnce(Return(false)); base::test::TestFuture<UserPermission> future; - UserContext user_context; - user_context.user_id = account.gaia; permission_service_->CanCollectSignals(user_context, future.GetCallback()); EXPECT_EQ(future.Get(), UserPermission::kUnaffiliated); } @@ -155,47 +143,17 @@ TEST_F(UserPermissionServiceImplTest, // browser's org. TEST_F(UserPermissionServiceImplTest, CanCollectSignals_BrowserManaged_ProfileUser_Affiliated) { - // Create known account. - AccountInfo account = identity_test_env_.MakeAccountAvailableWithCookies( - kUserEmail, kUserGaiaId); - - // Make sure there is a hosted domain. - account.hosted_domain = kHostedDomain; - identity_test_env_.UpdateAccountInfoForAccount(account); + UserContext user_context; + user_context.user_id = kUserGaiaId; - EXPECT_CALL(*mock_user_delegate_, IsSameManagedUser(account)) + EXPECT_CALL(*mock_user_delegate_, IsSameUser(kUserGaiaId)) .WillOnce(Return(true)); + EXPECT_CALL(*mock_user_delegate_, IsManaged()).WillOnce(Return(true)); EXPECT_CALL(*mock_user_delegate_, IsAffiliated()).WillOnce(Return(true)); base::test::TestFuture<UserPermission> future; - UserContext user_context; - user_context.user_id = account.gaia; permission_service_->CanCollectSignals(user_context, future.GetCallback()); EXPECT_EQ(future.Get(), UserPermission::kGranted); } -// Tests CanCollectSignals with a managed user ID and the browser is managed, -// but the user is not the Profile user. -// This is missing the remote affiliation check at the moment an defaults to -// "unaffiliated". -TEST_F(UserPermissionServiceImplTest, - CanCollectSignals_BrowserManaged_NotProfile) { - // Create known account. - AccountInfo account = identity_test_env_.MakeAccountAvailableWithCookies( - kUserEmail, kUserGaiaId); - - // Make sure there is a hosted domain. - account.hosted_domain = kHostedDomain; - identity_test_env_.UpdateAccountInfoForAccount(account); - - EXPECT_CALL(*mock_user_delegate_, IsSameManagedUser(account)) - .WillOnce(Return(false)); - - base::test::TestFuture<UserPermission> future; - UserContext user_context; - user_context.user_id = account.gaia; - permission_service_->CanCollectSignals(user_context, future.GetCallback()); - EXPECT_EQ(future.Get(), UserPermission::kUnaffiliated); -} - } // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/win/win_signals_collector.cc b/chromium/components/device_signals/core/browser/win/win_signals_collector.cc index 67ce37a9476..f6951ac4822 100644 --- a/chromium/components/device_signals/core/browser/win/win_signals_collector.cc +++ b/chromium/components/device_signals/core/browser/win/win_signals_collector.cc @@ -4,99 +4,92 @@ #include "components/device_signals/core/browser/win/win_signals_collector.h" +#include <functional> + #include "base/bind.h" #include "base/check.h" -#include "base/values.h" +#include "components/device_signals/core/browser/signals_types.h" #include "components/device_signals/core/browser/system_signals_service_host.h" #include "components/device_signals/core/common/mojom/system_signals.mojom.h" -#include "components/device_signals/core/common/signals_constants.h" +#include "components/device_signals/core/common/win/win_types.h" namespace device_signals { WinSignalsCollector::WinSignalsCollector( SystemSignalsServiceHost* system_service_host) - : system_service_host_(system_service_host), - signals_collection_map_({ - {names::kAntiVirusInfo, + : BaseSignalsCollector({ + {SignalName::kAntiVirus, base::BindRepeating(&WinSignalsCollector::GetAntiVirusSignal, base::Unretained(this))}, - {names::kInstalledHotfixes, + {SignalName::kHotfixes, base::BindRepeating(&WinSignalsCollector::GetHotfixSignal, base::Unretained(this))}, - }) { + }), + system_service_host_(system_service_host) { DCHECK(system_service_host_); } WinSignalsCollector::~WinSignalsCollector() = default; -const std::unordered_set<std::string> -WinSignalsCollector::GetSupportedSignalNames() { - std::unordered_set<std::string> supported_signals; - for (const auto& collection_pair : signals_collection_map_) { - supported_signals.insert(collection_pair.first); - } - - return supported_signals; -} - -void WinSignalsCollector::GetSignal(const std::string& signal_name, - const base::Value& params, - GetSignalCallback callback) { - const auto it = signals_collection_map_.find(signal_name); - if (it == signals_collection_map_.end()) { - std::move(callback).Run(base::Value(errors::kUnsupported)); - return; - } - - it->second.Run(params, std::move(callback)); -} - -void WinSignalsCollector::GetAntiVirusSignal(const base::Value& params, - GetSignalCallback callback) { +void WinSignalsCollector::GetAntiVirusSignal( + const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure) { auto* system_signals_service = system_service_host_->GetService(); if (!system_signals_service) { - std::move(callback).Run(base::Value(errors::kMissingSystemService)); + AntiVirusSignalResponse av_response; + av_response.collection_error = SignalCollectionError::kMissingSystemService; + response.av_signal_response = std::move(av_response); + std::move(done_closure).Run(); return; } - system_signals_service->GetAntiVirusSignals( - base::BindOnce(&WinSignalsCollector::OnAntiVirusSignalCollected, - weak_factory_.GetWeakPtr(), std::move(callback))); + system_signals_service->GetAntiVirusSignals(base::BindOnce( + &WinSignalsCollector::OnAntiVirusSignalCollected, + weak_factory_.GetWeakPtr(), std::ref(response), std::move(done_closure))); } void WinSignalsCollector::OnAntiVirusSignalCollected( - GetSignalCallback callback, + SignalsAggregationResponse& response, + base::OnceClosure done_closure, const std::vector<AvProduct>& av_products) { - base::Value::List av_values; - for (const auto& av_product : av_products) { - av_values.Append(av_product.ToValue()); - } + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + AntiVirusSignalResponse av_response; + av_response.av_products = std::move(av_products); + response.av_signal_response = std::move(av_response); - std::move(callback).Run(base::Value(std::move(av_values))); + std::move(done_closure).Run(); } -void WinSignalsCollector::GetHotfixSignal(const base::Value& params, - GetSignalCallback callback) { +void WinSignalsCollector::GetHotfixSignal( + const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure) { auto* system_signals_service = system_service_host_->GetService(); if (!system_signals_service) { - std::move(callback).Run(base::Value(errors::kMissingSystemService)); + HotfixSignalResponse hotfix_response; + hotfix_response.collection_error = + SignalCollectionError::kMissingSystemService; + response.hotfix_signal_response = std::move(hotfix_response); + std::move(done_closure).Run(); return; } - system_signals_service->GetHotfixSignals( - base::BindOnce(&WinSignalsCollector::OnHotfixSignalCollected, - weak_factory_.GetWeakPtr(), std::move(callback))); + system_signals_service->GetHotfixSignals(base::BindOnce( + &WinSignalsCollector::OnHotfixSignalCollected, weak_factory_.GetWeakPtr(), + std::ref(response), std::move(done_closure))); } void WinSignalsCollector::OnHotfixSignalCollected( - GetSignalCallback callback, + SignalsAggregationResponse& response, + base::OnceClosure done_closure, const std::vector<InstalledHotfix>& hotfixes) { - base::Value::List hotfix_values; - for (const auto& hotfix : hotfixes) { - hotfix_values.Append(hotfix.ToValue()); - } + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + HotfixSignalResponse hotfix_response; + hotfix_response.hotfixes = std::move(hotfixes); + response.hotfix_signal_response = std::move(hotfix_response); - std::move(callback).Run(base::Value(std::move(hotfix_values))); + std::move(done_closure).Run(); } } // namespace device_signals diff --git a/chromium/components/device_signals/core/browser/win/win_signals_collector.h b/chromium/components/device_signals/core/browser/win/win_signals_collector.h index c80f6e8be94..d88d766103b 100644 --- a/chromium/components/device_signals/core/browser/win/win_signals_collector.h +++ b/chromium/components/device_signals/core/browser/win/win_signals_collector.h @@ -5,24 +5,21 @@ #ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_WIN_WIN_SIGNALS_COLLECTOR_H_ #define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_WIN_WIN_SIGNALS_COLLECTOR_H_ -#include <map> #include <vector> #include "base/memory/weak_ptr.h" -#include "components/device_signals/core/browser/signals_collector.h" -#include "components/device_signals/core/common/win/win_types.h" - -namespace base { -class Value; -} // namespace base +#include "base/sequence_checker.h" +#include "components/device_signals/core/browser/base_signals_collector.h" namespace device_signals { +struct AvProduct; +struct InstalledHotfix; class SystemSignalsServiceHost; // Collector in charge of collecting device signals that are either specific to // Windows, or require a specific Windows-only implementation. -class WinSignalsCollector : public SignalsCollector { +class WinSignalsCollector : public BaseSignalsCollector { public: explicit WinSignalsCollector(SystemSignalsServiceHost* system_service_host); @@ -31,45 +28,43 @@ class WinSignalsCollector : public SignalsCollector { WinSignalsCollector(const WinSignalsCollector&) = delete; WinSignalsCollector& operator=(const WinSignalsCollector&) = delete; - // SignalsCollector: - const std::unordered_set<std::string> GetSupportedSignalNames() override; - void GetSignal(const std::string& signal_name, - const base::Value& params, - GetSignalCallback callback) override; - private: - // Collection function for the AV signal. `params` is ignored since AV signal - // does not require parameters. `callback` will be invoked when the signal - // values are available, or when something went wrong. - void GetAntiVirusSignal(const base::Value& params, - GetSignalCallback callback); + // Collection function for the Antivirus signal. `request` is ignored since AV + // signal does not require parameters. `response` will be passed along and the + // signal values will be set on it when available. `done_closure` will be + // invoked when signal collection is complete. + void GetAntiVirusSignal(const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure); // Invoked when the SystemSignalsService returns the collected AV signals as - // `av_products`. Will convert the structs to base::Value and invoke - // `callback`. - void OnAntiVirusSignalCollected(GetSignalCallback callback, + // `av_products`. Will update `response` with the signal collection outcome, + // and invoke `done_closure` to asynchronously notify the caller of the + // completion of this request. + void OnAntiVirusSignalCollected(SignalsAggregationResponse& response, + base::OnceClosure done_closure, const std::vector<AvProduct>& av_products); - // Collection function for the Hotfix signal. `params` is ignored since Hotfix - // signal does not require parameters. `callback` will be invoked when the - // signal values are available, or when something went wrong. - void GetHotfixSignal(const base::Value& params, GetSignalCallback callback); + // Collection function for the Hotfix signal. `request` is ignored since + // Hotfix signal does not require parameters. `response` will be passed along + // and the signal values will be set on it when available. `done_closure` will + // be invoked when signal collection is complete. + void GetHotfixSignal(const SignalsAggregationRequest& request, + SignalsAggregationResponse& response, + base::OnceClosure done_closure); // Invoked when the SystemSignalsService returns the collected Hotfix signals - // as `hotfixes`. Will convert the structs to base::Value and invoke - // `callback`. - void OnHotfixSignalCollected(GetSignalCallback callback, + // as `hotfixes`. Will update `response` with the signal collection outcome, + // and invoke `done_closure` to asynchronously notify the caller of the + // completion of this request. + void OnHotfixSignalCollected(SignalsAggregationResponse& response, + base::OnceClosure done_closure, const std::vector<InstalledHotfix>& hotfixes); // Instance used to retrieve a pointer to a SystemSignalsService instance. base::raw_ptr<SystemSignalsServiceHost> system_service_host_; - // Map used to forward signal collection requests to the right function keyed - // from a given signal name. - std::map<const std::string, - base::RepeatingCallback<void(const base::Value&, GetSignalCallback)>> - signals_collection_map_; - + SEQUENCE_CHECKER(sequence_checker_); base::WeakPtrFactory<WinSignalsCollector> weak_factory_{this}; }; diff --git a/chromium/components/device_signals/core/browser/win/win_signals_collector_unittest.cc b/chromium/components/device_signals/core/browser/win/win_signals_collector_unittest.cc index cd95070e9f9..ea6ab72d5cb 100644 --- a/chromium/components/device_signals/core/browser/win/win_signals_collector_unittest.cc +++ b/chromium/components/device_signals/core/browser/win/win_signals_collector_unittest.cc @@ -6,10 +6,11 @@ #include <array> +#include "base/run_loop.h" #include "base/test/task_environment.h" -#include "base/test/test_future.h" #include "base/values.h" #include "components/device_signals/core/browser/mock_system_signals_service_host.h" +#include "components/device_signals/core/browser/signals_types.h" #include "components/device_signals/core/common/signals_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -32,6 +33,12 @@ class WinSignalsCollectorTest : public testing::Test { ON_CALL(service_host_, GetService()).WillByDefault(Return(&service_)); } + SignalsAggregationRequest CreateRequest(SignalName signal_name) { + SignalsAggregationRequest request; + request.signal_names.emplace(signal_name); + return request; + } + base::test::TaskEnvironment task_environment_; StrictMock<MockSystemSignalsServiceHost> service_host_; @@ -42,8 +49,8 @@ class WinSignalsCollectorTest : public testing::Test { // Test that runs a sanity check on the set of signals supported by this // collector. Will need to be updated if new signals become supported. TEST_F(WinSignalsCollectorTest, SupportedSignalNames) { - const std::array<std::string, 2> supported_signals{ - {names::kAntiVirusInfo, names::kInstalledHotfixes}}; + const std::array<SignalName, 2> supported_signals{ + {SignalName::kAntiVirus, SignalName::kHotfixes}}; const auto names_set = win_collector_.GetSupportedSignalNames(); @@ -55,22 +62,57 @@ TEST_F(WinSignalsCollectorTest, SupportedSignalNames) { // Tests that an unsupported signal is marked as unsupported. TEST_F(WinSignalsCollectorTest, GetSignal_Unsupported) { - base::test::TestFuture<base::Value> future; - win_collector_.GetSignal(names::kDeviceId, base::Value(), - future.GetCallback()); - EXPECT_EQ(future.Get(), base::Value(errors::kUnsupported)); + SignalName signal_name = SignalName::kFileSystemInfo; + SignalsAggregationResponse response; + base::RunLoop run_loop; + win_collector_.GetSignal(signal_name, CreateRequest(signal_name), response, + run_loop.QuitClosure()); + + run_loop.Run(); + + ASSERT_TRUE(response.top_level_error.has_value()); + EXPECT_EQ(response.top_level_error.value(), + SignalCollectionError::kUnsupported); } // Tests that not being able to retrieve a pointer to the SystemSignalsService // returns an error. -TEST_F(WinSignalsCollectorTest, GetSignal_MissingSystemSignalsService) { - for (const auto& signal_name : win_collector_.GetSupportedSignalNames()) { - EXPECT_CALL(service_host_, GetService()).WillOnce(Return(nullptr)); +TEST_F(WinSignalsCollectorTest, GetSignal_AV_MissingSystemSignalsService) { + EXPECT_CALL(service_host_, GetService()).WillOnce(Return(nullptr)); + + SignalName signal_name = SignalName::kAntiVirus; + SignalsAggregationResponse response; + base::RunLoop run_loop; + win_collector_.GetSignal(signal_name, CreateRequest(signal_name), response, + run_loop.QuitClosure()); + + run_loop.Run(); + + ASSERT_FALSE(response.top_level_error.has_value()); + ASSERT_TRUE(response.av_signal_response.has_value()); + ASSERT_TRUE(response.av_signal_response->collection_error.has_value()); + EXPECT_EQ(response.av_signal_response->collection_error.value(), + SignalCollectionError::kMissingSystemService); +} - base::test::TestFuture<base::Value> future; - win_collector_.GetSignal(signal_name, base::Value(), future.GetCallback()); - EXPECT_EQ(future.Get(), base::Value(errors::kMissingSystemService)); - } +// Tests that not being able to retrieve a pointer to the SystemSignalsService +// returns an error. +TEST_F(WinSignalsCollectorTest, GetSignal_Hotfix_MissingSystemSignalsService) { + EXPECT_CALL(service_host_, GetService()).WillOnce(Return(nullptr)); + + SignalName signal_name = SignalName::kHotfixes; + SignalsAggregationResponse response; + base::RunLoop run_loop; + win_collector_.GetSignal(signal_name, CreateRequest(signal_name), response, + run_loop.QuitClosure()); + + run_loop.Run(); + + ASSERT_FALSE(response.top_level_error.has_value()); + ASSERT_TRUE(response.hotfix_signal_response.has_value()); + ASSERT_TRUE(response.hotfix_signal_response->collection_error.has_value()); + EXPECT_EQ(response.hotfix_signal_response->collection_error.value(), + SignalCollectionError::kMissingSystemService); } // Tests a successful AV signal retrieval. @@ -86,15 +128,20 @@ TEST_F(WinSignalsCollectorTest, GetSignal_AV) { std::move(signal_callback).Run(av_products); })); - base::test::TestFuture<base::Value> future; - win_collector_.GetSignal(names::kAntiVirusInfo, base::Value(), - future.GetCallback()); + SignalName signal_name = SignalName::kAntiVirus; + SignalsAggregationResponse response; + base::RunLoop run_loop; + win_collector_.GetSignal(signal_name, CreateRequest(signal_name), response, + run_loop.QuitClosure()); - const base::Value& response = future.Get(); - ASSERT_TRUE(response.is_list()); - const base::Value::List& list_value = response.GetList(); - ASSERT_EQ(list_value.size(), av_products.size()); - EXPECT_EQ(list_value[0], av_products[0].ToValue()); + run_loop.Run(); + + EXPECT_FALSE(response.top_level_error.has_value()); + ASSERT_TRUE(response.av_signal_response.has_value()); + EXPECT_FALSE(response.av_signal_response->collection_error.has_value()); + EXPECT_EQ(response.av_signal_response->av_products.size(), + av_products.size()); + EXPECT_EQ(response.av_signal_response->av_products[0], av_products[0]); } // Tests a successful hotfix signal retrieval. @@ -108,15 +155,19 @@ TEST_F(WinSignalsCollectorTest, GetSignal_Hotfix) { std::move(signal_callback).Run(hotfixes); })); - base::test::TestFuture<base::Value> future; - win_collector_.GetSignal(names::kInstalledHotfixes, base::Value(), - future.GetCallback()); + SignalName signal_name = SignalName::kHotfixes; + SignalsAggregationResponse response; + base::RunLoop run_loop; + win_collector_.GetSignal(signal_name, CreateRequest(signal_name), response, + run_loop.QuitClosure()); + + run_loop.Run(); - const base::Value& response = future.Get(); - ASSERT_TRUE(response.is_list()); - const base::Value::List& list_value = response.GetList(); - ASSERT_EQ(list_value.size(), hotfixes.size()); - EXPECT_EQ(list_value[0], hotfixes[0].ToValue()); + EXPECT_FALSE(response.top_level_error.has_value()); + ASSERT_TRUE(response.hotfix_signal_response.has_value()); + EXPECT_FALSE(response.hotfix_signal_response->collection_error.has_value()); + EXPECT_EQ(response.hotfix_signal_response->hotfixes.size(), hotfixes.size()); + EXPECT_EQ(response.hotfix_signal_response->hotfixes[0], hotfixes[0]); } } // namespace device_signals diff --git a/chromium/components/device_signals/core/common/BUILD.gn b/chromium/components/device_signals/core/common/BUILD.gn index 3d707b775e4..1a808cf3684 100644 --- a/chromium/components/device_signals/core/common/BUILD.gn +++ b/chromium/components/device_signals/core/common/BUILD.gn @@ -3,15 +3,42 @@ # found in the LICENSE file. static_library("common") { - public = [ "signals_constants.h" ] + public = [ + "common_types.h", + "signals_constants.h", + ] - sources = [ "signals_constants.cc" ] + sources = [ + "common_types.cc", + "signals_constants.cc", + ] + + public_deps = [ "//third_party/abseil-cpp:absl" ] + + deps = [ "//base" ] +} + +source_set("features") { + public = [ "signals_features.h" ] + + sources = [ "signals_features.cc" ] + + public_deps = [ "//base" ] } source_set("unit_tests") { testonly = true + sources = [ "signals_features_unittest.cc" ] + deps = [ + ":common", + ":features", + "//base", + "//base/test:test_support", + "//testing/gmock", + "//testing/gtest", + ] if (is_win) { - deps = [ "//components/device_signals/core/common/win:unit_tests" ] + deps += [ "//components/device_signals/core/common/win:unit_tests" ] } } diff --git a/chromium/components/device_signals/core/common/common_types.cc b/chromium/components/device_signals/core/common/common_types.cc new file mode 100644 index 00000000000..078724ea3a2 --- /dev/null +++ b/chromium/components/device_signals/core/common/common_types.cc @@ -0,0 +1,52 @@ +// Copyright 2022 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 "components/device_signals/core/common/common_types.h" + +namespace device_signals { + +ExecutableMetadata::ExecutableMetadata() = default; + +ExecutableMetadata::ExecutableMetadata(const ExecutableMetadata&) = default; +ExecutableMetadata& ExecutableMetadata::operator=(const ExecutableMetadata&) = + default; + +ExecutableMetadata::~ExecutableMetadata() = default; + +bool ExecutableMetadata::operator==(const ExecutableMetadata& other) const { + return is_running == other.is_running && + public_key_sha256 == other.public_key_sha256 && + product_name == other.product_name && version == other.version; +} + +FileSystemItem::FileSystemItem() = default; + +FileSystemItem::FileSystemItem(const FileSystemItem&) = default; +FileSystemItem& FileSystemItem::operator=(const FileSystemItem&) = default; + +FileSystemItem::~FileSystemItem() = default; + +bool FileSystemItem::operator==(const FileSystemItem& other) const { + return file_path == other.file_path && presence == other.presence && + sha256_hash == other.sha256_hash && + executable_metadata == other.executable_metadata; +} + +GetFileSystemInfoOptions::GetFileSystemInfoOptions() = default; + +GetFileSystemInfoOptions::GetFileSystemInfoOptions( + const GetFileSystemInfoOptions&) = default; +GetFileSystemInfoOptions& GetFileSystemInfoOptions::operator=( + const GetFileSystemInfoOptions&) = default; + +GetFileSystemInfoOptions::~GetFileSystemInfoOptions() = default; + +bool GetFileSystemInfoOptions::operator==( + const GetFileSystemInfoOptions& other) const { + return file_path == other.file_path && + compute_sha256 == other.compute_sha256 && + compute_executable_metadata == other.compute_executable_metadata; +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/common/common_types.h b/chromium/components/device_signals/core/common/common_types.h new file mode 100644 index 00000000000..f085163573f --- /dev/null +++ b/chromium/components/device_signals/core/common/common_types.h @@ -0,0 +1,89 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_COMMON_TYPES_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_COMMON_TYPES_H_ + +#include <string> + +#include "base/files/file_path.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace device_signals { + +// Used to indicate whether a given signal was correctly found or not, or +// indicate a reason for not being able to find it. +enum class PresenceValue { kUnspecified, kAccessDenied, kNotFound, kFound }; + +struct ExecutableMetadata { + ExecutableMetadata(); + + ExecutableMetadata(const ExecutableMetadata&); + ExecutableMetadata& operator=(const ExecutableMetadata&); + + ~ExecutableMetadata(); + + // Is true if a currently running process was spawned from this file. + bool is_running = false; + + // Byte string containing the SHA256 hash of the public key of the certificate + // used to sign the executable. + absl::optional<std::string> public_key_sha256 = absl::nullopt; + + // Product name of this executable. + absl::optional<std::string> product_name = absl::nullopt; + + // Version of this executable. + absl::optional<std::string> version = absl::nullopt; + + bool operator==(const ExecutableMetadata& other) const; +}; + +struct FileSystemItem { + FileSystemItem(); + + FileSystemItem(const FileSystemItem&); + FileSystemItem& operator=(const FileSystemItem&); + + ~FileSystemItem(); + + // Path to the file system object for whom those signals were collected. + base::FilePath file_path{}; + + // Value indicating whether the specific resource could be found or not. + PresenceValue presence = PresenceValue::kUnspecified; + + // Byte string containing the SHA256 hash of a file’s bytes. Ignored when + // `path` points to a directory. Collected only when `compute_sha256` is set + // to true in the corresponding GetFileSystemInfoOptions parameter. + absl::optional<std::string> sha256_hash = absl::nullopt; + + // Set of properties only relevant for executable files. Will only be + // collected if computeIsExecutable is set to true in the given signals + // collection parameters and if `path` points to an executable file. + absl::optional<ExecutableMetadata> executable_metadata = absl::nullopt; + + bool operator==(const FileSystemItem& other) const; +}; + +struct GetFileSystemInfoOptions { + GetFileSystemInfoOptions(); + + GetFileSystemInfoOptions(const GetFileSystemInfoOptions&); + GetFileSystemInfoOptions& operator=(const GetFileSystemInfoOptions&); + + ~GetFileSystemInfoOptions(); + + base::FilePath file_path{}; + + bool compute_sha256 = false; + + bool compute_executable_metadata = false; + + bool operator==(const GetFileSystemInfoOptions& other) const; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_COMMON_TYPES_H_ diff --git a/chromium/components/device_signals/core/common/mojom/BUILD.gn b/chromium/components/device_signals/core/common/mojom/BUILD.gn index 2d9e2cda1f0..0c98d32fec7 100644 --- a/chromium/components/device_signals/core/common/mojom/BUILD.gn +++ b/chromium/components/device_signals/core/common/mojom/BUILD.gn @@ -12,8 +12,38 @@ mojom("mojom") { "//sandbox/policy/mojom", ] + cpp_typemaps = [ + { + types = [ + { + mojom = "device_signals.mojom.PresenceValue" + cpp = "::device_signals::PresenceValue" + }, + { + mojom = "device_signals.mojom.ExecutableMetadata" + cpp = "::device_signals::ExecutableMetadata" + }, + { + mojom = "device_signals.mojom.FileSystemItem" + cpp = "::device_signals::FileSystemItem" + }, + { + mojom = "device_signals.mojom.FileSystemItemRequest" + cpp = "::device_signals::GetFileSystemInfoOptions" + }, + ] + traits_headers = [ "system_signals_mojom_traits_common.h" ] + traits_sources = [ "system_signals_mojom_traits_common.cc" ] + traits_public_deps = [ + "//base", + "//components/device_signals/core/common", + "//mojo/public/cpp/base:shared_typemap_traits", + ] + }, + ] + if (is_win) { - cpp_typemaps = [ + cpp_typemaps += [ { types = [ { @@ -29,8 +59,8 @@ mojom("mojom") { cpp = "::device_signals::InstalledHotfix" }, ] - traits_headers = [ "system_signals_mojom_traits.h" ] - traits_sources = [ "system_signals_mojom_traits.cc" ] + traits_headers = [ "system_signals_mojom_traits_win.h" ] + traits_sources = [ "system_signals_mojom_traits_win.cc" ] traits_public_deps = [ "//base", "//components/device_signals/core/common/win", diff --git a/chromium/components/device_signals/core/common/mojom/DEPS b/chromium/components/device_signals/core/common/mojom/DEPS new file mode 100644 index 00000000000..093b1d9fde5 --- /dev/null +++ b/chromium/components/device_signals/core/common/mojom/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/public/cpp", +] diff --git a/chromium/components/device_signals/core/common/mojom/system_signals.mojom b/chromium/components/device_signals/core/common/mojom/system_signals.mojom index f335e665684..0967e937b6e 100644 --- a/chromium/components/device_signals/core/common/mojom/system_signals.mojom +++ b/chromium/components/device_signals/core/common/mojom/system_signals.mojom @@ -4,32 +4,45 @@ module device_signals.mojom; -import "mojo/public/mojom/base/file_path.mojom"; import "sandbox/policy/mojom/sandbox.mojom"; +import "mojo/public/mojom/base/byte_string.mojom"; +import "mojo/public/mojom/base/file_path.mojom"; -enum TrileanValue { - kFalse, - kTrue, - kUnknown -}; +enum PresenceValue { + // Was unable to determine whether the signal source exists or not. This + // typically indicates that a failure occurred before even trying to assess + // its presence. + kUnspecified, -struct BinarySignalsRequest { - mojo_base.mojom.FilePath file_path; -}; + // Unable to determine the signal source's existence due to insufficient user + // permissions. + kAccessDenied, -struct BinarySignalsResponse { - mojo_base.mojom.FilePath file_path; + // The signal's source does not exist. + kNotFound, - bool is_running; + // The signal's source was found. + kFound +}; - [EnableIfNot=is_linux] - string product_name; +struct ExecutableMetadata { + bool is_running; + mojo_base.mojom.ByteString? public_key_sha256; + string? product_name; + string? version; +}; - [EnableIfNot=is_linux] - string public_key_sha256; +struct FileSystemItem { + mojo_base.mojom.FilePath file_path; + PresenceValue presence; + mojo_base.mojom.ByteString? sha256_hash; + ExecutableMetadata? executable_metadata; +}; - [EnableIfNot=is_linux] - string version; +struct FileSystemItemRequest { + mojo_base.mojom.FilePath file_path; + bool compute_sha256; + bool compute_executable_metadata; }; [EnableIf=is_win] @@ -64,10 +77,10 @@ struct HotfixSignal { // This service can be accessed from the browser process. [ServiceSandbox=sandbox.mojom.Sandbox.kNoSandbox] interface SystemSignalsService { - // Collects signals about a set of binary files specified by `requests`. - // Returns the collected information in `response`. - GetBinarySignals(array<BinarySignalsRequest> requests) - => (array<BinarySignalsResponse> response); + // Collects signals about a set of file system items specified by `requests`. + // Returns the collected information in `items`. + GetFileSystemSignals(array<FileSystemItemRequest> requests) + => (array<FileSystemItem> items); // Collects information about AntiVirus software installed on the current // device. Returns that information via `av_signals`. diff --git a/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_common.cc b/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_common.cc new file mode 100644 index 00000000000..64780ef9926 --- /dev/null +++ b/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_common.cc @@ -0,0 +1,104 @@ +// Copyright 2022 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 "components/device_signals/core/common/mojom/system_signals_mojom_traits_common.h" + +#include "base/notreached.h" +#include "mojo/public/cpp/base/byte_string_mojom_traits.h" +#include "mojo/public/cpp/base/file_path_mojom_traits.h" + +namespace mojo { + +// static +device_signals::mojom::PresenceValue +EnumTraits<device_signals::mojom::PresenceValue, + device_signals::PresenceValue>::ToMojom(device_signals::PresenceValue + input) { + switch (input) { + case device_signals::PresenceValue::kUnspecified: + return device_signals::mojom::PresenceValue::kUnspecified; + case device_signals::PresenceValue::kAccessDenied: + return device_signals::mojom::PresenceValue::kAccessDenied; + case device_signals::PresenceValue::kNotFound: + return device_signals::mojom::PresenceValue::kNotFound; + case device_signals::PresenceValue::kFound: + return device_signals::mojom::PresenceValue::kFound; + } +} + +// static +bool EnumTraits<device_signals::mojom::PresenceValue, + device_signals::PresenceValue>:: + FromMojom(device_signals::mojom::PresenceValue input, + device_signals::PresenceValue* output) { + absl::optional<device_signals::PresenceValue> parsed_value; + switch (input) { + case device_signals::mojom::PresenceValue::kUnspecified: + parsed_value = device_signals::PresenceValue::kUnspecified; + break; + case device_signals::mojom::PresenceValue::kAccessDenied: + parsed_value = device_signals::PresenceValue::kAccessDenied; + break; + case device_signals::mojom::PresenceValue::kNotFound: + parsed_value = device_signals::PresenceValue::kNotFound; + break; + case device_signals::mojom::PresenceValue::kFound: + parsed_value = device_signals::PresenceValue::kFound; + break; + } + + if (parsed_value.has_value()) { + *output = parsed_value.value(); + return true; + } + return false; +} + +// static +bool StructTraits<device_signals::mojom::ExecutableMetadataDataView, + device_signals::ExecutableMetadata>:: + Read(device_signals::mojom::ExecutableMetadataDataView data, + device_signals::ExecutableMetadata* output) { + output->is_running = data.is_running(); + + if (!data.ReadPublicKeySha256(&output->public_key_sha256) || + !data.ReadProductName(&output->product_name) || + !data.ReadVersion(&output->version)) { + return false; + } + + return true; +} + +// static +bool StructTraits<device_signals::mojom::FileSystemItemDataView, + device_signals::FileSystemItem>:: + Read(device_signals::mojom::FileSystemItemDataView data, + device_signals::FileSystemItem* output) { + if (!data.ReadFilePath(&output->file_path) || + !data.ReadPresence(&output->presence) || + !data.ReadSha256Hash(&output->sha256_hash) || + !data.ReadExecutableMetadata(&output->executable_metadata)) { + return false; + } + + return true; +} + +// static +bool StructTraits<device_signals::mojom::FileSystemItemRequestDataView, + device_signals::GetFileSystemInfoOptions>:: + Read(device_signals::mojom::FileSystemItemRequestDataView data, + device_signals::GetFileSystemInfoOptions* output) { + output->compute_sha256 = data.compute_sha256(); + output->compute_executable_metadata = data.compute_executable_metadata(); + + if (!data.ReadFilePath(&output->file_path)) { + return false; + } + + return true; +} + +} // namespace mojo diff --git a/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_common.h b/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_common.h new file mode 100644 index 00000000000..fedcc7b84ed --- /dev/null +++ b/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_common.h @@ -0,0 +1,103 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_COMMON_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_COMMON_H_ + +#include <string> + +#include "base/files/file_path.h" +#include "components/device_signals/core/common/common_types.h" +#include "components/device_signals/core/common/mojom/system_signals.mojom-shared.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace mojo { + +template <> +struct EnumTraits<device_signals::mojom::PresenceValue, + device_signals::PresenceValue> { + static device_signals::mojom::PresenceValue ToMojom( + device_signals::PresenceValue input); + static bool FromMojom(device_signals::mojom::PresenceValue input, + device_signals::PresenceValue* output); +}; + +template <> +struct StructTraits<device_signals::mojom::ExecutableMetadataDataView, + device_signals::ExecutableMetadata> { + static bool is_running(const device_signals::ExecutableMetadata& input) { + return input.is_running; + } + + static absl::optional<std::string> public_key_sha256( + const device_signals::ExecutableMetadata& input) { + return input.public_key_sha256; + } + + static absl::optional<std::string> product_name( + const device_signals::ExecutableMetadata& input) { + return input.product_name; + } + + static absl::optional<std::string> version( + const device_signals::ExecutableMetadata& input) { + return input.version; + } + + static bool Read(device_signals::mojom::ExecutableMetadataDataView data, + device_signals::ExecutableMetadata* output); +}; + +template <> +struct StructTraits<device_signals::mojom::FileSystemItemDataView, + device_signals::FileSystemItem> { + static const base::FilePath& file_path( + const device_signals::FileSystemItem& input) { + return input.file_path; + } + + static device_signals::PresenceValue presence( + const device_signals::FileSystemItem& input) { + return input.presence; + } + + static absl::optional<std::string> sha256_hash( + const device_signals::FileSystemItem& input) { + return input.sha256_hash; + } + + static absl::optional<device_signals::ExecutableMetadata> executable_metadata( + const device_signals::FileSystemItem& input) { + return input.executable_metadata; + } + + static bool Read(device_signals::mojom::FileSystemItemDataView input, + device_signals::FileSystemItem* output); +}; + +template <> +struct StructTraits<device_signals::mojom::FileSystemItemRequestDataView, + device_signals::GetFileSystemInfoOptions> { + static const base::FilePath& file_path( + const device_signals::GetFileSystemInfoOptions& input) { + return input.file_path; + } + + static bool compute_sha256( + const device_signals::GetFileSystemInfoOptions& input) { + return input.compute_sha256; + } + + static bool compute_executable_metadata( + const device_signals::GetFileSystemInfoOptions& input) { + return input.compute_executable_metadata; + } + + static bool Read(device_signals::mojom::FileSystemItemRequestDataView input, + device_signals::GetFileSystemInfoOptions* output); +}; + +} // namespace mojo + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_COMMON_H_ diff --git a/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits.cc b/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_win.cc index 22daec5f4ce..6d14d5b91ee 100644 --- a/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits.cc +++ b/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_win.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/device_signals/core/common/mojom/system_signals_mojom_traits.h" +#include "components/device_signals/core/common/mojom/system_signals_mojom_traits_win.h" #include "base/notreached.h" #include "third_party/abseil-cpp/absl/types/optional.h" diff --git a/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits.h b/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_win.h index 7b1f5469a03..0600be46eb8 100644 --- a/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits.h +++ b/chromium/components/device_signals/core/common/mojom/system_signals_mojom_traits_win.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_H_ -#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_H_ +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_WIN_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_WIN_H_ #include <string> @@ -56,4 +56,4 @@ struct StructTraits<device_signals::mojom::HotfixSignalDataView, } // namespace mojo -#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_H_
\ No newline at end of file +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_MOJOM_SYSTEM_SIGNALS_MOJOM_TRAITS_WIN_H_ diff --git a/chromium/components/device_signals/core/common/signals_constants.cc b/chromium/components/device_signals/core/common/signals_constants.cc index 021c7b759fb..bf2c5c72969 100644 --- a/chromium/components/device_signals/core/common/signals_constants.cc +++ b/chromium/components/device_signals/core/common/signals_constants.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. @@ -144,6 +144,10 @@ namespace errors { // to be collected. const char kConsentRequired[] = "CONSENT_REQUIRED"; +// Returned when the user does not represent the current browser user, or is +// not managed. +const char kInvalidUser[] = "INVALID_USER"; + // Returned when the user is not affiliated with the organization managing the // browser. const char kUnaffiliatedUser[] = "UNAFFILIATED_USER"; @@ -155,6 +159,14 @@ const char kUnsupported[] = "UNSUPPORTED"; // the SystemSignalsService. const char kMissingSystemService[] = "MISSING_SYSTEM_SERVICE"; +// Returned when the signals aggregation response is missing a +// bundle/sub-response struct that was expected by a specific use-case. +const char kMissingBundle[] = "MISSING_BUNDLE"; + +// Returned when requesting the collection of a parameterized signal without +// parameters. +const char kMissingParameters[] = "MISSING_PARAMETERS"; + } // namespace errors } // namespace device_signals diff --git a/chromium/components/device_signals/core/common/signals_constants.h b/chromium/components/device_signals/core/common/signals_constants.h index b78cdba4562..ad4ade35852 100644 --- a/chromium/components/device_signals/core/common/signals_constants.h +++ b/chromium/components/device_signals/core/common/signals_constants.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. @@ -52,8 +52,11 @@ namespace errors { extern const char kConsentRequired[]; extern const char kUnaffiliatedUser[]; +extern const char kInvalidUser[]; extern const char kUnsupported[]; extern const char kMissingSystemService[]; +extern const char kMissingBundle[]; +extern const char kMissingParameters[]; } // namespace errors diff --git a/chromium/components/device_signals/core/common/signals_features.cc b/chromium/components/device_signals/core/common/signals_features.cc new file mode 100644 index 00000000000..cfb98c6223d --- /dev/null +++ b/chromium/components/device_signals/core/common/signals_features.cc @@ -0,0 +1,55 @@ +// Copyright 2022 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 "components/device_signals/core/common/signals_features.h" + +namespace enterprise_signals::features { + +const base::Feature kNewEvSignalsEnabled = {"NewEvSignalsEnabled", + base::FEATURE_DISABLED_BY_DEFAULT}; + +const base::FeatureParam<bool> kDisableFileSystemInfo{ + &kNewEvSignalsEnabled, "DisableFileSystemInfo", false}; +const base::FeatureParam<bool> kDisableSettings{&kNewEvSignalsEnabled, + "DisableSettings", false}; +const base::FeatureParam<bool> kDisableAntiVirus{&kNewEvSignalsEnabled, + "DisableAntiVirus", false}; +const base::FeatureParam<bool> kDisableHotfix{&kNewEvSignalsEnabled, + "DisableHotfix", false}; + +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) +// Enables the consent promo for sharing device signal when a managed user +// signs in on an unmanaged device. This occurs after the sign-in intercept +// and before the sync promo (if enabled) +// This feature also requires UnmanagedDeviceSignalsConsentFlowEnabled policy to +// be enabled +const base::Feature kDeviceSignalsPromoAfterSigninIntercept{ + "DeviceSignalsPromoAfterSigninIntercept", + base::FEATURE_DISABLED_BY_DEFAULT}; +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) + +bool IsNewFunctionEnabled(NewEvFunction new_ev_function) { + if (!base::FeatureList::IsEnabled(kNewEvSignalsEnabled)) { + return false; + } + + bool disable_function = false; + switch (new_ev_function) { + case NewEvFunction::kFileSystemInfo: + disable_function = kDisableFileSystemInfo.Get(); + break; + case NewEvFunction::kSettings: + disable_function = kDisableSettings.Get(); + break; + case NewEvFunction::kAntiVirus: + disable_function = kDisableAntiVirus.Get(); + break; + case NewEvFunction::kHotfix: + disable_function = kDisableHotfix.Get(); + break; + } + return !disable_function; +} + +} // namespace enterprise_signals::features diff --git a/chromium/components/device_signals/core/common/signals_features.h b/chromium/components/device_signals/core/common/signals_features.h new file mode 100644 index 00000000000..a3dc50abb0f --- /dev/null +++ b/chromium/components/device_signals/core/common/signals_features.h @@ -0,0 +1,36 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_SIGNALS_FEATURES_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_SIGNALS_FEATURES_H_ + +#include "base/feature_list.h" +#include "base/metrics/field_trial_params.h" + +namespace enterprise_signals::features { + +// Feature flag for new private SecureConnect functions exposing additional +// device signals. +extern const base::Feature kNewEvSignalsEnabled; + +// Feature parameters that can be used to turn off individual functions. +extern const base::FeatureParam<bool> kDisableFileSystemInfo; +extern const base::FeatureParam<bool> kDisableSettings; +extern const base::FeatureParam<bool> kDisableAntiVirus; +extern const base::FeatureParam<bool> kDisableHotfix; + +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) +extern const base::Feature kDeviceSignalsPromoAfterSigninIntercept; +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) + +// Enum used to map a given function to its kill switch. +enum class NewEvFunction { kFileSystemInfo, kSettings, kAntiVirus, kHotfix }; + +// Returns true if the function pointed at by `new_ev_function` is considered +// to be enabled based on the feature flag and its parameters. +bool IsNewFunctionEnabled(NewEvFunction new_ev_function); + +} // namespace enterprise_signals::features + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_SIGNALS_FEATURES_H_ diff --git a/chromium/components/device_signals/core/common/signals_features_unittest.cc b/chromium/components/device_signals/core/common/signals_features_unittest.cc new file mode 100644 index 00000000000..7ecf6d6c80d --- /dev/null +++ b/chromium/components/device_signals/core/common/signals_features_unittest.cc @@ -0,0 +1,89 @@ +// Copyright 2022 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 "components/device_signals/core/common/signals_features.h" + +#include "base/test/scoped_feature_list.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace enterprise_signals::features { + +class SignalsFeaturesTest : public testing::Test { + protected: + const int min_enum_value_ = static_cast<int>(NewEvFunction::kFileSystemInfo); + const int max_enum_value_ = static_cast<int>(NewEvFunction::kHotfix); + base::test::ScopedFeatureList scoped_features_; +}; + +// Tests that IsNewFunctionEnabled will return false when the feature flag is +// disabled. +TEST_F(SignalsFeaturesTest, DisabledFeature) { + scoped_features_.InitAndDisableFeature(kNewEvSignalsEnabled); + for (int i = min_enum_value_; i <= max_enum_value_; i++) { + EXPECT_FALSE(IsNewFunctionEnabled(static_cast<NewEvFunction>(i))); + } +} + +// Tests that IsNewFunctionEnabled will return true when the feature flag is +// enabled, and no specific function is disabled. +TEST_F(SignalsFeaturesTest, EnabledFeature) { + scoped_features_.InitAndEnableFeature(kNewEvSignalsEnabled); + for (int i = min_enum_value_; i <= max_enum_value_; i++) { + EXPECT_TRUE(IsNewFunctionEnabled(static_cast<NewEvFunction>(i))); + } +} + +// Tests how IsNewFunctionEnabled behaves when the feature flag is enabled, but +// the FileSystemInfo function is disabled. +TEST_F(SignalsFeaturesTest, Enabled_DisableFileSystemInfo) { + scoped_features_.InitAndEnableFeatureWithParameters( + kNewEvSignalsEnabled, {{"DisableFileSystemInfo", "true"}}); + + for (int i = min_enum_value_; i <= max_enum_value_; i++) { + NewEvFunction current_function = static_cast<NewEvFunction>(i); + EXPECT_EQ(IsNewFunctionEnabled(current_function), + current_function != NewEvFunction::kFileSystemInfo); + } +} + +// Tests how IsNewFunctionEnabled behaves when the feature flag is enabled, but +// the Settings function is disabled. +TEST_F(SignalsFeaturesTest, Enabled_DisableSetting) { + scoped_features_.InitAndEnableFeatureWithParameters( + kNewEvSignalsEnabled, {{"DisableSettings", "true"}}); + + for (int i = min_enum_value_; i <= max_enum_value_; i++) { + NewEvFunction current_function = static_cast<NewEvFunction>(i); + EXPECT_EQ(IsNewFunctionEnabled(current_function), + current_function != NewEvFunction::kSettings); + } +} + +// Tests how IsNewFunctionEnabled behaves when the feature flag is enabled, but +// the Antivirus function is disabled. +TEST_F(SignalsFeaturesTest, Enabled_DisableAntiVirus) { + scoped_features_.InitAndEnableFeatureWithParameters( + kNewEvSignalsEnabled, {{"DisableAntiVirus", "true"}}); + + for (int i = min_enum_value_; i <= max_enum_value_; i++) { + NewEvFunction current_function = static_cast<NewEvFunction>(i); + EXPECT_EQ(IsNewFunctionEnabled(current_function), + current_function != NewEvFunction::kAntiVirus); + } +} + +// Tests how IsNewFunctionEnabled behaves when the feature flag is enabled, but +// the Hotfix function is disabled. +TEST_F(SignalsFeaturesTest, Enabled_DisableHotfix) { + scoped_features_.InitAndEnableFeatureWithParameters( + kNewEvSignalsEnabled, {{"DisableHotfix", "true"}}); + + for (int i = min_enum_value_; i <= max_enum_value_; i++) { + NewEvFunction current_function = static_cast<NewEvFunction>(i); + EXPECT_EQ(IsNewFunctionEnabled(current_function), + current_function != NewEvFunction::kHotfix); + } +} + +} // namespace enterprise_signals::features diff --git a/chromium/components/device_signals/core/common/win/BUILD.gn b/chromium/components/device_signals/core/common/win/BUILD.gn index 048ed98ffa6..dfdcd91886c 100644 --- a/chromium/components/device_signals/core/common/win/BUILD.gn +++ b/chromium/components/device_signals/core/common/win/BUILD.gn @@ -3,63 +3,20 @@ # found in the LICENSE file. static_library("win") { - public = [ - "win_types.h", - "wmi_client.h", - "wmi_client_impl.h", - "wsc_client.h", - "wsc_client_impl.h", - ] + public = [ "win_types.h" ] - sources = [ - "win_types.cc", - "wmi_client.cc", - "wmi_client_impl.cc", - "wsc_client.cc", - "wsc_client_impl.cc", - ] + sources = [ "win_types.cc" ] - public_deps = [ - "//base", - "//third_party/abseil-cpp:absl", - ] -} - -source_set("test_support") { - testonly = true - sources = [ - "com_fakes.cc", - "com_fakes.h", - "mock_wmi_client.cc", - "mock_wmi_client.h", - "mock_wsc_client.cc", - "mock_wsc_client.h", - ] - - deps = [ - ":win", - "//base", - "//testing/gmock", - "//testing/gtest", - "//third_party/abseil-cpp:absl", - ] + deps = [ "//base" ] } source_set("unit_tests") { testonly = true - sources = [ - "win_types_unittest.cc", - "wmi_client_impl_unittest.cc", - "wsc_client_impl_unittest.cc", - ] + sources = [ "win_types_unittest.cc" ] deps = [ - ":test_support", ":win", "//base", - "//base/test:test_support", - "//testing/gmock", "//testing/gtest", - "//third_party/abseil-cpp:absl", ] } diff --git a/chromium/components/device_signals/core/common/win/mock_wmi_client.h b/chromium/components/device_signals/core/common/win/mock_wmi_client.h deleted file mode 100644 index ac934b7b9a4..00000000000 --- a/chromium/components/device_signals/core/common/win/mock_wmi_client.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2022 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. - -#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_MOCK_WMI_CLIENT_H_ -#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_MOCK_WMI_CLIENT_H_ - -#include "components/device_signals/core/common/win/wmi_client.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace device_signals { - -class MockWmiClient : public WmiClient { - public: - MockWmiClient(); - ~MockWmiClient() override; - - MOCK_METHOD(WmiHotfixesResponse, GetInstalledHotfixes, (), (override)); -}; - -} // namespace device_signals - -#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_MOCK_WMI_CLIENT_H_ diff --git a/chromium/components/device_signals/core/common/win/win_types.cc b/chromium/components/device_signals/core/common/win/win_types.cc index 8add6652e48..18b1c574282 100644 --- a/chromium/components/device_signals/core/common/win/win_types.cc +++ b/chromium/components/device_signals/core/common/win/win_types.cc @@ -8,6 +8,11 @@ namespace device_signals { +bool AvProduct::operator==(const AvProduct& other) const { + return display_name == other.display_name && state == other.state && + product_id == other.product_id; +} + base::Value AvProduct::ToValue() const { base::Value::Dict values; values.Set("displayName", display_name); @@ -16,6 +21,10 @@ base::Value AvProduct::ToValue() const { return base::Value(std::move(values)); } +bool InstalledHotfix::operator==(const InstalledHotfix& other) const { + return hotfix_id == other.hotfix_id; +} + base::Value InstalledHotfix::ToValue() const { base::Value::Dict values; values.Set("hotfixId", hotfix_id); diff --git a/chromium/components/device_signals/core/common/win/win_types.h b/chromium/components/device_signals/core/common/win/win_types.h index d551d48d2b5..a90b01ca080 100644 --- a/chromium/components/device_signals/core/common/win/win_types.h +++ b/chromium/components/device_signals/core/common/win/win_types.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. @@ -20,14 +20,18 @@ enum class AvProductState { kOn, kOff, kSnoozed, kExpired }; // Can be retrieve via WSC on Windows 8 and above, and below properties are // collected via this interface: // https://docs.microsoft.com/en-us/windows/win32/api/iwscapi/nn-iwscapi-iwscproduct +// On Win7 and below, this can be retrieve by an undocumented method in WMI, +// which goes through the SecurityCenter2 WMI server. struct AvProduct { - std::string display_name; - AvProductState state; + std::string display_name{}; + AvProductState state = AvProductState::kOff; // Although not present on the documentation, IWscProduct exposes a // `get_ProductGuid` function to retrieve an GUID representing an Antivirus // software. - std::string product_id; + std::string product_id{}; + + bool operator==(const AvProduct& other) const; base::Value ToValue() const; }; @@ -37,7 +41,9 @@ struct InstalledHotfix { // In WMI, this value represents the `HotFixID` property from entries in // "Win32_QuickFixEngineering". They have a format looking like `KB123123`. // https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-quickfixengineering - std::string hotfix_id; + std::string hotfix_id{}; + + bool operator==(const InstalledHotfix& other) const; base::Value ToValue() const; }; diff --git a/chromium/components/device_signals/core/common/win/wmi_client_impl.cc b/chromium/components/device_signals/core/common/win/wmi_client_impl.cc deleted file mode 100644 index 40b68264e2e..00000000000 --- a/chromium/components/device_signals/core/common/win/wmi_client_impl.cc +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2022 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 "components/device_signals/core/common/win/wmi_client_impl.h" - -#include <wbemidl.h> -#include <windows.h> -#include <wrl/client.h> - -#include "base/bind.h" -#include "base/callback.h" -#include "base/check.h" -#include "base/metrics/histogram_functions.h" -#include "base/strings/sys_string_conversions.h" -#include "base/threading/scoped_blocking_call.h" -#include "base/win/com_init_util.h" -#include "base/win/scoped_bstr.h" -#include "base/win/scoped_variant.h" -#include "base/win/windows_version.h" -#include "base/win/wmi.h" -#include "third_party/abseil-cpp/absl/types/optional.h" - -using Microsoft::WRL::ComPtr; - -namespace device_signals { - -namespace { - -// Parses a string value from `class_object` named `property_name`. -absl::optional<std::string> ParseString( - const std::wstring& property_name, - const ComPtr<IWbemClassObject>& class_object) { - base::win::ScopedVariant string_variant; - HRESULT hr = class_object->Get(property_name.c_str(), 0, - string_variant.Receive(), 0, 0); - - if (FAILED(hr) || string_variant.type() != VT_BSTR) { - return absl::nullopt; - } - - // Owned by ScopedVariant. - BSTR temp_bstr = V_BSTR(string_variant.ptr()); - return base::SysWideToUTF8( - std::wstring(temp_bstr, ::SysStringLen(temp_bstr))); -} - -} // namespace - -WmiClientImpl::WmiClientImpl() - : run_query_callback_(base::BindRepeating(base::win::RunWmiQuery)) {} - -WmiClientImpl::WmiClientImpl(RunWmiQueryCallback run_query_callback) - : run_query_callback_(std::move(run_query_callback)) {} - -WmiClientImpl::~WmiClientImpl() = default; - -WmiHotfixesResponse WmiClientImpl::GetInstalledHotfixes() { - base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, - base::BlockingType::MAY_BLOCK); - ComPtr<IEnumWbemClassObject> enumerator; - auto result_code = run_query_callback_.Run( - base::win::kCimV2ServerName, L"SELECT * FROM Win32_QuickFixEngineering", - &enumerator); - - WmiHotfixesResponse response; - if (result_code.has_value()) { - response.query_error = result_code.value(); - return response; - } - - HRESULT hr; - while (true) { - ComPtr<IWbemClassObject> class_object; - ULONG items_returned = 0U; - hr = enumerator->Next(WBEM_INFINITE, 1, &class_object, &items_returned); - - if (hr == WBEM_S_FALSE || items_returned == 0U) { - // Reached the end of the enumerator. - break; - } - - // Something went wrong, and it wasn't the end of the enumerator. - if (FAILED(hr)) { - response.parsing_errors.push_back( - WmiParsingError::kFailedToIterateResults); - continue; - } - - absl::optional<std::string> hotfix_id = - ParseString(L"HotFixId", class_object); - if (!hotfix_id.has_value()) { - response.parsing_errors.push_back(WmiParsingError::kFailedToGetName); - continue; - } - - InstalledHotfix hotfix; - hotfix.hotfix_id = hotfix_id.value(); - - // If all values were parsed properly, add `hotfix` into the response - // vector. If any value could not be parsed properly, the item was - // discarded. - response.hotfixes.push_back(hotfix); - } - - return response; -} - -} // namespace device_signals diff --git a/chromium/components/device_signals/core/common/win/wmi_client_impl.h b/chromium/components/device_signals/core/common/win/wmi_client_impl.h deleted file mode 100644 index fa931472c97..00000000000 --- a/chromium/components/device_signals/core/common/win/wmi_client_impl.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2022 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. - -#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WMI_CLIENT_IMPL_H_ -#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WMI_CLIENT_IMPL_H_ - -#include "base/callback.h" -#include "components/device_signals/core/common/win/wmi_client.h" - -// WMI interfaces are available on Windows Vista and above, and are officially -// undocumented. -namespace device_signals { - -class WmiClientImpl : public WmiClient { - public: - using RunWmiQueryCallback = - base::RepeatingCallback<absl::optional<base::win::WmiError>( - const std::wstring&, - const std::wstring&, - Microsoft::WRL::ComPtr<IEnumWbemClassObject>*)>; - - WmiClientImpl(); - - ~WmiClientImpl() override; - - // WmiClient: - WmiHotfixesResponse GetInstalledHotfixes() override; - - private: - friend class WmiClientImplTest; - - // Constructor taking in a `run_query_callback` which can be used to mock - // running the WMI query. - explicit WmiClientImpl(RunWmiQueryCallback run_query_callback); - - RunWmiQueryCallback run_query_callback_; -}; - -} // namespace device_signals - -#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WMI_CLIENT_IMPL_H_ diff --git a/chromium/components/device_signals/core/common/win/wmi_client_impl_unittest.cc b/chromium/components/device_signals/core/common/win/wmi_client_impl_unittest.cc deleted file mode 100644 index cd83f1f4f7b..00000000000 --- a/chromium/components/device_signals/core/common/win/wmi_client_impl_unittest.cc +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2022 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 "components/device_signals/core/common/win/wmi_client_impl.h" - -#include <wbemidl.h> -#include <windows.h> -#include <wrl/client.h> -#include <wrl/implements.h> -#include <memory> -#include <vector> - -#include "base/bind.h" -#include "base/callback.h" -#include "base/strings/sys_string_conversions.h" -#include "base/test/task_environment.h" -#include "base/win/wmi.h" -#include "components/device_signals/core/common/win/com_fakes.h" -#include "components/device_signals/core/common/win/win_types.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/abseil-cpp/absl/types/optional.h" - -using Microsoft::WRL::ComPtr; - -namespace device_signals { - -namespace { - -FakeWbemClassObject CreateHotfixObject(const std::wstring& hotfix_id) { - FakeWbemClassObject hotfix_obj; - hotfix_obj.Set(L"HotFixId", hotfix_id); - return hotfix_obj; -} - -} // namespace - -class WmiClientImplTest : public testing::Test { - public: - WmiClientImplTest() - : wmi_client_(base::BindRepeating(&WmiClientImplTest::RunQuery, - base::Unretained(this))) {} - - protected: - absl::optional<base::win::WmiError> RunQuery( - const std::wstring& server_name, - const std::wstring& query, - ComPtr<IEnumWbemClassObject>* enumerator) { - ++nb_calls_; - captured_server_name_ = server_name; - captured_query_ = query; - - if (query_error_.has_value()) { - return query_error_.value(); - } - *enumerator = &fake_enumerator_; - return absl::nullopt; - } - - void ExpectHotfixQueryRan() { - EXPECT_EQ(captured_server_name_, base::win::kCimV2ServerName); - EXPECT_EQ(captured_query_, L"SELECT * FROM Win32_QuickFixEngineering"); - EXPECT_EQ(nb_calls_, 1U); - } - - FakeEnumWbemClassObject fake_enumerator_; - absl::optional<base::win::WmiError> query_error_; - - std::wstring captured_server_name_; - std::wstring captured_query_; - uint32_t nb_calls_ = 0; - - WmiClientImpl wmi_client_; -}; - -// Tests how the client behaves when the WMI query fails when querying for -// installed hotfixes. -TEST_F(WmiClientImplTest, GetInstalledHotfixes_FailedRunQuery) { - query_error_ = base::win::WmiError::kFailedToConnectToWMI; - - auto hotfix_response = wmi_client_.GetInstalledHotfixes(); - - ExpectHotfixQueryRan(); - EXPECT_EQ(hotfix_response.hotfixes.size(), 0U); - EXPECT_EQ(hotfix_response.parsing_errors.size(), 0U); - EXPECT_EQ(hotfix_response.query_error, - base::win::WmiError::kFailedToConnectToWMI); -} - -// Tests how the client behaves when parsing hotfix objects, one of which is -// valid, and another which is missing an ID. -TEST_F(WmiClientImplTest, GetInstalledHotfixes_ParsingItems) { - std::wstring hotfix_id1 = L"some_hotfix_id"; - FakeWbemClassObject hotfix_obj1 = CreateHotfixObject(hotfix_id1); - - FakeWbemClassObject hotfix_obj2 = CreateHotfixObject(L"some_other_id"); - - hotfix_obj2.Delete(L"HotFixId"); - - fake_enumerator_.Add(&hotfix_obj1); - fake_enumerator_.Add(&hotfix_obj2); - - auto hotfix_response = wmi_client_.GetInstalledHotfixes(); - - ExpectHotfixQueryRan(); - EXPECT_EQ(hotfix_response.query_error, absl::nullopt); - - // Success item. - ASSERT_EQ(hotfix_response.hotfixes.size(), 1U); - EXPECT_EQ(hotfix_response.hotfixes[0].hotfix_id, - base::SysWideToUTF8(hotfix_id1)); - - // Failed item. - ASSERT_EQ(hotfix_response.parsing_errors.size(), 1U); - EXPECT_EQ(hotfix_response.parsing_errors[0], - WmiParsingError::kFailedToGetName); -} - -} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/BUILD.gn b/chromium/components/device_signals/core/system_signals/BUILD.gn new file mode 100644 index 00000000000..59de635bdd1 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/BUILD.gn @@ -0,0 +1,80 @@ +# Copyright 2022 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. + +static_library("system_signals") { + public = [ + "file_system_service.h", + "platform_delegate.h", + "platform_utils.h", + ] + + sources = [ + "file_system_service.cc", + "hashing_utils.cc", + "hashing_utils.h", + "platform_delegate.cc", + ] + + if (is_win || is_mac || is_linux) { + public += [ "base_platform_delegate.h" ] + + sources += [ "base_platform_delegate.cc" ] + } + + if (is_win) { + sources += [ "win/platform_utils_win.cc" ] + } + + if (is_mac || is_linux) { + sources += [ "posix/platform_utils_posix.cc" ] + } + + public_deps = [ "//third_party/abseil-cpp:absl" ] + + deps = [ + "//base", + "//components/device_signals/core/common", + "//crypto", + ] +} + +source_set("test_support") { + testonly = true + sources = [ + "mock_file_system_service.cc", + "mock_file_system_service.h", + "mock_platform_delegate.cc", + "mock_platform_delegate.h", + ] + + deps = [ + ":system_signals", + "//base", + "//components/device_signals/core/common", + "//testing/gmock", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ "file_system_service_unittest.cc" ] + + if (is_win || is_mac || is_linux) { + sources += [ "base_platform_delegate_unittest.cc" ] + } + + deps = [ + ":system_signals", + ":test_support", + "//base", + "//base/test:test_support", + "//components/device_signals/core/common", + "//testing/gmock", + "//testing/gtest", + ] + + if (is_win) { + deps += [ "//components/device_signals/core/system_signals/win:unit_tests" ] + } +} diff --git a/chromium/components/device_signals/core/system_signals/base_platform_delegate.cc b/chromium/components/device_signals/core/system_signals/base_platform_delegate.cc new file mode 100644 index 00000000000..5f2340ec92e --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/base_platform_delegate.cc @@ -0,0 +1,80 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/base_platform_delegate.h" + +#include "base/containers/flat_map.h" +#include "base/containers/flat_set.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/process/process_iterator.h" +#include "components/device_signals/core/common/common_types.h" +#include "components/device_signals/core/system_signals/platform_utils.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace device_signals { + +BasePlatformDelegate::BasePlatformDelegate() = default; +BasePlatformDelegate::~BasePlatformDelegate() = default; + +bool BasePlatformDelegate::PathIsReadable( + const base::FilePath& file_path) const { + return base::PathIsReadable(file_path); +} + +bool BasePlatformDelegate::DirectoryExists( + const base::FilePath& file_path) const { + return base::DirectoryExists(file_path); +} + +FilePathMap<bool> BasePlatformDelegate::AreExecutablesRunning( + const FilePathSet& file_paths) { + // Initialize map with the given file paths. + FilePathMap<bool> running_map; + running_map.reserve(file_paths.size()); + for (const auto& file_path : file_paths) { + // Default initialize as not running. + running_map[file_path] = false; + } + + // Use counter to keep track of how many entries were found, which can allow + // for an earlier return if all executables were to be found early. + size_t counter = 0; + base::ProcessIterator process_iterator(nullptr); + while (const auto* process_entry = process_iterator.NextProcessEntry()) { + if (counter >= running_map.size()) { + // Found all items we were looking for, so return early. + break; + } + + absl::optional<base::FilePath> exe_path = + GetProcessExePath(process_entry->pid()); + if (exe_path && running_map.contains(exe_path.value())) { + ++counter; + running_map[exe_path.value()] = true; + } + } + + return running_map; +} + +FilePathMap<ExecutableMetadata> BasePlatformDelegate::GetAllExecutableMetadata( + const FilePathSet& file_paths) { + FilePathMap<bool> files_are_running_map = AreExecutablesRunning(file_paths); + + FilePathMap<ExecutableMetadata> file_paths_to_metadata_map; + for (const auto& file_path : file_paths) { + ExecutableMetadata executable_metadata; + + if (files_are_running_map.contains(file_path)) { + executable_metadata.is_running = files_are_running_map[file_path]; + } + + file_paths_to_metadata_map[file_path] = executable_metadata; + } + + return file_paths_to_metadata_map; +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/base_platform_delegate.h b/chromium/components/device_signals/core/system_signals/base_platform_delegate.h new file mode 100644 index 00000000000..cbc08cc82c3 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/base_platform_delegate.h @@ -0,0 +1,35 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_BASE_PLATFORM_DELEGATE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_BASE_PLATFORM_DELEGATE_H_ + +#include "components/device_signals/core/system_signals/platform_delegate.h" + +namespace device_signals { + +// Implements some functionality that is common to all PlatformDelegate +// specializations. +class BasePlatformDelegate : public PlatformDelegate { + public: + ~BasePlatformDelegate() override; + + // PlatformDelegate: + bool PathIsReadable(const base::FilePath& file_path) const override; + bool DirectoryExists(const base::FilePath& file_path) const override; + FilePathMap<ExecutableMetadata> GetAllExecutableMetadata( + const FilePathSet& file_paths) override; + + protected: + BasePlatformDelegate(); + + // Returns a map of file paths to whether a currently running process was + // spawned from that file. The set of file paths in the map are specified by + // `file_paths`. + FilePathMap<bool> AreExecutablesRunning(const FilePathSet& file_paths); +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_BASE_PLATFORM_DELEGATE_H_ diff --git a/chromium/components/device_signals/core/system_signals/base_platform_delegate_unittest.cc b/chromium/components/device_signals/core/system_signals/base_platform_delegate_unittest.cc new file mode 100644 index 00000000000..af048537b93 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/base_platform_delegate_unittest.cc @@ -0,0 +1,55 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/base_platform_delegate.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "components/device_signals/core/common/common_types.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device_signals { + +namespace { + +constexpr base::FilePath::CharType kFileName[] = FILE_PATH_LITERAL("test_file"); + +} // namespace + +class TestPlatformDelegate : public BasePlatformDelegate { + public: + TestPlatformDelegate() = default; + ~TestPlatformDelegate() override = default; + + // Uninterested in pure virtual functions. + MOCK_METHOD(bool, + ResolveFilePath, + (const base::FilePath&, base::FilePath*), + (override)); +}; + +class BasePlatformDelegateTest : public testing::Test { + protected: + BasePlatformDelegateTest() { + EXPECT_TRUE(scoped_dir_.CreateUniqueTempDir()); + file_path_ = scoped_dir_.GetPath().Append(kFileName); + EXPECT_TRUE(base::WriteFile(file_path_, "irrelevant file content")); + } + + base::ScopedTempDir scoped_dir_; + base::FilePath file_path_; + TestPlatformDelegate platform_delegate_; +}; + +// Sanity checks for functions that wrap base namespace functions. Only tests +// success cases as underlying functions are tested on their own. +TEST_F(BasePlatformDelegateTest, SanityChecks) { + EXPECT_TRUE(platform_delegate_.PathIsReadable(file_path_)); + EXPECT_FALSE(platform_delegate_.DirectoryExists(file_path_)); + EXPECT_TRUE(platform_delegate_.DirectoryExists(scoped_dir_.GetPath())); +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/file_system_service.cc b/chromium/components/device_signals/core/system_signals/file_system_service.cc new file mode 100644 index 00000000000..d638a868ba6 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/file_system_service.cc @@ -0,0 +1,133 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/file_system_service.h" + +#include <memory> +#include <utility> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "components/device_signals/core/common/common_types.h" +#include "components/device_signals/core/system_signals/hashing_utils.h" +#include "components/device_signals/core/system_signals/platform_delegate.h" + +namespace device_signals { + +namespace { + +std::vector<FileSystemItem> GetAllItems( + const FilePathMap<std::vector<FileSystemItem>>& map) { + std::vector<FileSystemItem> items; + for (const auto& pair : map) { + items.insert(items.end(), pair.second.begin(), pair.second.end()); + } + return items; +} + +} // namespace + +class FileSystemServiceImpl : public FileSystemService { + public: + explicit FileSystemServiceImpl(std::unique_ptr<PlatformDelegate> delegate); + ~FileSystemServiceImpl() override; + + // FileSystemService: + std::vector<FileSystemItem> GetSignals( + const std::vector<GetFileSystemInfoOptions>& options) override; + PresenceValue ResolveFileSystemItem( + const base::FilePath& original_file_path, + base::FilePath* resolved_file_path) const override; + + private: + std::unique_ptr<PlatformDelegate> delegate_; +}; + +// static +std::unique_ptr<FileSystemService> FileSystemService::Create( + std::unique_ptr<PlatformDelegate> delegate) { + return std::make_unique<FileSystemServiceImpl>(std::move(delegate)); +} + +FileSystemServiceImpl::FileSystemServiceImpl( + std::unique_ptr<PlatformDelegate> delegate) + : delegate_(std::move(delegate)) {} + +FileSystemServiceImpl::~FileSystemServiceImpl() = default; + +std::vector<FileSystemItem> FileSystemServiceImpl::GetSignals( + const std::vector<GetFileSystemInfoOptions>& options) { + // Keeping a map of resolved file paths to their corresponding + // FileSystemItem objects is required in order to call the batch + // GetAllExecutableMetadata API and having a way to map the resulting + // ExecutableMetadata back to the corresponding FileSystemItem. + // The value of the map is a vector since there is no guarantee that some of + // the resolved file paths don't point to the same file. + FilePathMap<std::vector<FileSystemItem>> resolved_paths_to_item_map; + FilePathSet executable_paths; + for (const auto& option : options) { + FileSystemItem collected_item; + collected_item.file_path = option.file_path; + + // The resolved file path will be used internally for signal collection, but + // won't be returned to the caller. + base::FilePath resolved_file_path; + collected_item.presence = + ResolveFileSystemItem(option.file_path, &resolved_file_path); + + // Only try to collect more signals if a file exists at the resolved path. + if (collected_item.presence == PresenceValue::kFound && + !base::DirectoryExists(resolved_file_path)) { + if (option.compute_sha256) { + collected_item.sha256_hash = HashFile(resolved_file_path); + } + + if (option.compute_executable_metadata) { + if (!executable_paths.contains(resolved_file_path)) { + executable_paths.insert(resolved_file_path); + } + } + } + + if (!resolved_paths_to_item_map.contains(resolved_file_path)) { + resolved_paths_to_item_map[resolved_file_path] = + std::vector<FileSystemItem>(); + } + + resolved_paths_to_item_map[resolved_file_path].push_back( + std::move(collected_item)); + } + + auto collected_executable_metadata = + delegate_->GetAllExecutableMetadata(executable_paths); + + for (const auto& path_metadata_pair : collected_executable_metadata) { + if (!resolved_paths_to_item_map.contains(path_metadata_pair.first)) { + continue; + } + + for (auto& collected_item : + resolved_paths_to_item_map[path_metadata_pair.first]) { + collected_item.executable_metadata = path_metadata_pair.second; + } + } + + return GetAllItems(resolved_paths_to_item_map); +} + +PresenceValue FileSystemServiceImpl::ResolveFileSystemItem( + const base::FilePath& original_file_path, + base::FilePath* resolved_file_path) const { + base::FilePath local_resolved_path; + if (delegate_->ResolveFilePath(original_file_path, &local_resolved_path)) { + *resolved_file_path = local_resolved_path; + if (delegate_->PathIsReadable(local_resolved_path)) { + return PresenceValue::kFound; + } + return PresenceValue::kAccessDenied; + } + return PresenceValue::kNotFound; +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/file_system_service.h b/chromium/components/device_signals/core/system_signals/file_system_service.h new file mode 100644 index 00000000000..f6a7903831e --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/file_system_service.h @@ -0,0 +1,45 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_FILE_SYSTEM_SERVICE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_FILE_SYSTEM_SERVICE_H_ + +#include <memory> +#include <vector> + +namespace base { +class FilePath; +} // namespace base + +namespace device_signals { + +enum class PresenceValue; +class PlatformDelegate; +struct FileSystemItem; +struct GetFileSystemInfoOptions; + +class FileSystemService { + public: + virtual ~FileSystemService() = default; + + // Creates a FileSystemService instance using the given `delegate`. + static std::unique_ptr<FileSystemService> Create( + std::unique_ptr<PlatformDelegate> delegate); + + // Collects and returns the file system items' signals as requested by + // `options`. + virtual std::vector<FileSystemItem> GetSignals( + const std::vector<GetFileSystemInfoOptions>& options) = 0; + + // Given an `original_file_path`, will attempt to resolve any environment + // variables, and store that result in `resolved_file_path`, then calculate + // the PresenceValue of that file system resource and return that. + virtual PresenceValue ResolveFileSystemItem( + const base::FilePath& original_file_path, + base::FilePath* resolved_file_path) const = 0; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_FILE_SYSTEM_SERVICE_H_ diff --git a/chromium/components/device_signals/core/system_signals/file_system_service_unittest.cc b/chromium/components/device_signals/core/system_signals/file_system_service_unittest.cc new file mode 100644 index 00000000000..57fd867aab9 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/file_system_service_unittest.cc @@ -0,0 +1,255 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/file_system_service.h" + +#include <array> +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "components/device_signals/core/common/common_types.h" +#include "components/device_signals/core/system_signals/mock_platform_delegate.h" +#include "components/device_signals/core/system_signals/platform_delegate.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Invoke; +using testing::Return; + +namespace device_signals { + +namespace { + +GetFileSystemInfoOptions CreateOptions(const base::FilePath& path, + bool compute_sha256, + bool compute_executable_metadata) { + GetFileSystemInfoOptions options; + options.file_path = path; + options.compute_sha256 = compute_sha256; + options.compute_executable_metadata = compute_executable_metadata; + return options; +} + +std::string HexEncodeHash(const std::string& hashed_data) { + return base::ToLowerASCII( + base::HexEncode(std::data(hashed_data), hashed_data.size())); +} + +absl::optional<size_t> FindItemIndexByFilePath( + const base::FilePath& expected_file_path, + const std::vector<FileSystemItem>& items) { + for (size_t i = 0; i < items.size(); i++) { + if (items[i].file_path == expected_file_path) { + return i; + } + } + return absl::nullopt; +} + +} // namespace + +class FileSystemServiceTest : public testing::Test { + protected: + FileSystemServiceTest() { + auto mock_platform_delegate = + std::make_unique<testing::StrictMock<MockPlatformDelegate>>(); + mock_platform_delegate_ = mock_platform_delegate.get(); + + file_system_service_ = + FileSystemService::Create(std::move(mock_platform_delegate)); + + ON_CALL(*mock_platform_delegate_, GetAllExecutableMetadata(FilePathSet())) + .WillByDefault(Return(FilePathMap<ExecutableMetadata>())); + } + + void ExpectResolvablePath(const base::FilePath& path, + const base::FilePath& resolved_path) { + EXPECT_CALL(*mock_platform_delegate_, ResolveFilePath(path, _)) + .WillOnce( + Invoke([&resolved_path](const base::FilePath& original_file_path, + base::FilePath* resolved_file_path) { + *resolved_file_path = resolved_path; + return true; + })); + } + + void ExpectPathIsReadable(const base::FilePath& path) { + EXPECT_CALL(*mock_platform_delegate_, PathIsReadable(path)) + .WillOnce(Return(true)); + } + + testing::StrictMock<MockPlatformDelegate>* mock_platform_delegate_; + std::unique_ptr<FileSystemService> file_system_service_; +}; + +// Tests all possible PresenceValue outcomes. +TEST_F(FileSystemServiceTest, GetSignals_Presence) { + base::FilePath unresolvable_file_path = + base::FilePath::FromUTF8Unsafe("/cannot/resolve"); + EXPECT_CALL(*mock_platform_delegate_, + ResolveFilePath(unresolvable_file_path, _)) + .WillOnce(Return(false)); + + base::FilePath access_denied_path = + base::FilePath::FromUTF8Unsafe("/cannot/access"); + base::FilePath access_denied_path_resolved = + base::FilePath::FromUTF8Unsafe("/cannot/access/resolved"); + ExpectResolvablePath(access_denied_path, access_denied_path_resolved); + EXPECT_CALL(*mock_platform_delegate_, + PathIsReadable(access_denied_path_resolved)) + .WillOnce(Return(false)); + + base::FilePath found_path = base::FilePath::FromUTF8Unsafe("/found"); + base::FilePath found_path_resolved = + base::FilePath::FromUTF8Unsafe("/found/resolved"); + ExpectResolvablePath(found_path, found_path_resolved); + ExpectPathIsReadable(found_path_resolved); + + std::vector<GetFileSystemInfoOptions> options; + options.push_back(CreateOptions(unresolvable_file_path, true, false)); + options.push_back(CreateOptions(access_denied_path, true, false)); + options.push_back(CreateOptions(found_path, false, false)); + + std::array<PresenceValue, 4> expected_presence_values{ + PresenceValue::kNotFound, PresenceValue::kAccessDenied, + PresenceValue::kFound}; + + EXPECT_CALL(*mock_platform_delegate_, + GetAllExecutableMetadata(FilePathSet())); + + auto file_system_items = file_system_service_->GetSignals(options); + + ASSERT_EQ(file_system_items.size(), options.size()); + + for (size_t i = 0; i < options.size(); i++) { + auto index = + FindItemIndexByFilePath(options[i].file_path, file_system_items); + ASSERT_TRUE(index.has_value()); + + const FileSystemItem& item = file_system_items[index.value()]; + EXPECT_EQ(item.presence, expected_presence_values[i]); + + // No metadata was collected, as it was only requested for the two files + // that cannot be found/accessed. + EXPECT_FALSE(item.sha256_hash.has_value()); + EXPECT_FALSE(item.executable_metadata.has_value()); + } +} + +// Tests calculating the hash for a file when requested, but not for a +// directory. +TEST_F(FileSystemServiceTest, GetSignals_Hash_Success) { + base::ScopedTempDir scoped_dir; + ASSERT_TRUE(scoped_dir.CreateUniqueTempDir()); + + constexpr base::FilePath::CharType existing_file[] = + FILE_PATH_LITERAL("existing_file"); + + const base::FilePath absolute_file_path = + scoped_dir.GetPath().Append(existing_file); + + // Write some content. + ASSERT_TRUE(base::WriteFile(absolute_file_path, "some file content")); + + ExpectResolvablePath(absolute_file_path, absolute_file_path); + ExpectPathIsReadable(absolute_file_path); + ExpectResolvablePath(scoped_dir.GetPath(), scoped_dir.GetPath()); + ExpectPathIsReadable(scoped_dir.GetPath()); + + std::vector<GetFileSystemInfoOptions> options; + options.push_back( + CreateOptions(absolute_file_path, /*compute_sha256=*/true, false)); + options.push_back( + CreateOptions(scoped_dir.GetPath(), /*compute_sha256=*/true, false)); + + EXPECT_CALL(*mock_platform_delegate_, + GetAllExecutableMetadata(FilePathSet())); + + auto file_system_items = file_system_service_->GetSignals(options); + + constexpr char expected_sha256_hash[] = + "b05ffa4eea8fb5609d576a68c1066be3f99e4dc53d365a0ac2a78259b2dd91f9"; + ASSERT_EQ(file_system_items.size(), options.size()); + + // The file's hash was computed. + auto index = FindItemIndexByFilePath(absolute_file_path, file_system_items); + ASSERT_TRUE(index.has_value()); + FileSystemItem& item = file_system_items[index.value()]; + EXPECT_EQ(item.presence, PresenceValue::kFound); + ASSERT_TRUE(item.sha256_hash.has_value()); + EXPECT_EQ(HexEncodeHash(item.sha256_hash.value()), expected_sha256_hash); + + // The directory does not have a hash. + index = FindItemIndexByFilePath(scoped_dir.GetPath(), file_system_items); + ASSERT_TRUE(index.has_value()); + item = file_system_items[index.value()]; + EXPECT_EQ(item.file_path, scoped_dir.GetPath()); + EXPECT_EQ(item.presence, PresenceValue::kFound); + EXPECT_FALSE(item.sha256_hash.has_value()); +} + +// Tests that the FileSystemService uses the PlatformDelegate to collect +// the ExecutableMetadata only when requested. +TEST_F(FileSystemServiceTest, GetSignals_ExecutableMetadata) { + base::FilePath first_found_path = + base::FilePath::FromUTF8Unsafe("/first/found"); + base::FilePath first_found_path_resolved = + base::FilePath::FromUTF8Unsafe("/first/found/resolved"); + ExpectResolvablePath(first_found_path, first_found_path_resolved); + ExpectPathIsReadable(first_found_path_resolved); + + base::FilePath second_found_path = + base::FilePath::FromUTF8Unsafe("/second/found"); + base::FilePath second_found_path_resolved = + base::FilePath::FromUTF8Unsafe("/second/found/resolved"); + ExpectResolvablePath(second_found_path, second_found_path_resolved); + ExpectPathIsReadable(second_found_path_resolved); + + ExecutableMetadata executable_metadata; + executable_metadata.is_running = true; + + FilePathSet expected_path_set; + expected_path_set.insert(first_found_path_resolved); + + FilePathMap<ExecutableMetadata> result_metadata_map; + result_metadata_map.insert({first_found_path_resolved, executable_metadata}); + + EXPECT_CALL(*mock_platform_delegate_, + GetAllExecutableMetadata(expected_path_set)) + .WillOnce(Return(result_metadata_map)); + + std::vector<GetFileSystemInfoOptions> options; + options.push_back(CreateOptions(first_found_path, false, + /*compute_executable_metadata=*/true)); + options.push_back(CreateOptions(second_found_path, false, + /*compute_executable_metadata=*/false)); + + auto file_system_items = file_system_service_->GetSignals(options); + + ASSERT_EQ(file_system_items.size(), options.size()); + + // The first file's executable metadata was properly retrieved via the + // mocked delegate. + auto index = FindItemIndexByFilePath(first_found_path, file_system_items); + ASSERT_TRUE(index.has_value()); + FileSystemItem& item = file_system_items[index.value()]; + EXPECT_EQ(item.presence, PresenceValue::kFound); + ASSERT_TRUE(item.executable_metadata.has_value()); + EXPECT_EQ(item.executable_metadata.value(), executable_metadata); + + // We did not request executable metadata from the second file, so it should + // be absl::nullopt. + index = FindItemIndexByFilePath(second_found_path, file_system_items); + ASSERT_TRUE(index.has_value()); + item = file_system_items[index.value()]; + EXPECT_EQ(item.presence, PresenceValue::kFound); + EXPECT_FALSE(item.executable_metadata.has_value()); +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/hashing_utils.cc b/chromium/components/device_signals/core/system_signals/hashing_utils.cc new file mode 100644 index 00000000000..3fd2ba6a438 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/hashing_utils.cc @@ -0,0 +1,48 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/hashing_utils.h" + +#include <array> +#include <string> +#include <vector> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/memory/page_size.h" +#include "base/threading/scoped_blocking_call.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" + +namespace device_signals { + +absl::optional<std::string> HashFile(const base::FilePath& file_path) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::MAY_BLOCK); + + base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) { + return absl::nullopt; + } + + auto secure_hash = + crypto::SecureHash::Create(crypto::SecureHash::Algorithm::SHA256); + std::vector<char> buffer(base::GetPageSize()); + + int bytes_read = 0; + do { + bytes_read = file.ReadAtCurrentPos(buffer.data(), buffer.size()); + if (bytes_read == -1) { + return absl::nullopt; + } + secure_hash->Update(buffer.data(), bytes_read); + } while (bytes_read > 0); + + std::string hash(crypto::kSHA256Length, 0); + secure_hash->Finish(std::data(hash), hash.size()); + return hash; +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/hashing_utils.h b/chromium/components/device_signals/core/system_signals/hashing_utils.h new file mode 100644 index 00000000000..92c7fee99e4 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/hashing_utils.h @@ -0,0 +1,24 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_HASHING_UTILS_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_HASHING_UTILS_H_ + +#include <string> + +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace base { +class FilePath; +} // namespace base + +namespace device_signals { + +// Tries to generate a byte string containing the SHA256 value of the file at +// `file_path`. Will return absl::nullopt in invalid cases. +absl::optional<std::string> HashFile(const base::FilePath& file_path); + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_HASHING_UTILS_H_ diff --git a/chromium/components/device_signals/core/system_signals/linux/BUILD.gn b/chromium/components/device_signals/core/system_signals/linux/BUILD.gn new file mode 100644 index 00000000000..a8b4da3765c --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/linux/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2022 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. + +static_library("linux") { + public = [ "linux_platform_delegate.h" ] + + sources = [ "linux_platform_delegate.cc" ] + + public_deps = [ + "//components/device_signals/core/common", + "//components/device_signals/core/system_signals", + "//components/device_signals/core/system_signals/posix", + ] + + deps = [ "//base" ] +} diff --git a/chromium/components/device_signals/core/system_signals/linux/linux_platform_delegate.cc b/chromium/components/device_signals/core/system_signals/linux/linux_platform_delegate.cc new file mode 100644 index 00000000000..a4e4da312cb --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/linux/linux_platform_delegate.cc @@ -0,0 +1,13 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/linux/linux_platform_delegate.h" + +namespace device_signals { + +LinuxPlatformDelegate::LinuxPlatformDelegate() = default; + +LinuxPlatformDelegate::~LinuxPlatformDelegate() = default; + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/linux/linux_platform_delegate.h b/chromium/components/device_signals/core/system_signals/linux/linux_platform_delegate.h new file mode 100644 index 00000000000..9fb85f3b644 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/linux/linux_platform_delegate.h @@ -0,0 +1,20 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_LINUX_LINUX_PLATFORM_DELEGATE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_LINUX_LINUX_PLATFORM_DELEGATE_H_ + +#include "components/device_signals/core/system_signals/posix/posix_platform_delegate.h" + +namespace device_signals { + +class LinuxPlatformDelegate : public PosixPlatformDelegate { + public: + LinuxPlatformDelegate(); + ~LinuxPlatformDelegate() override; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_LINUX_LINUX_PLATFORM_DELEGATE_H_ diff --git a/chromium/components/device_signals/core/system_signals/mac/BUILD.gn b/chromium/components/device_signals/core/system_signals/mac/BUILD.gn new file mode 100644 index 00000000000..a9fe889fad6 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/mac/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2022 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. + +static_library("mac") { + public = [ "mac_platform_delegate.h" ] + + sources = [ "mac_platform_delegate.mm" ] + + public_deps = [ + "//components/device_signals/core/common", + "//components/device_signals/core/system_signals", + "//components/device_signals/core/system_signals/posix", + ] + + deps = [ "//base" ] +} diff --git a/chromium/components/device_signals/core/system_signals/mac/mac_platform_delegate.h b/chromium/components/device_signals/core/system_signals/mac/mac_platform_delegate.h new file mode 100644 index 00000000000..2bc1887e658 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/mac/mac_platform_delegate.h @@ -0,0 +1,20 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MAC_MAC_PLATFORM_DELEGATE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MAC_MAC_PLATFORM_DELEGATE_H_ + +#include "components/device_signals/core/system_signals/posix/posix_platform_delegate.h" + +namespace device_signals { + +class MacPlatformDelegate : public PosixPlatformDelegate { + public: + MacPlatformDelegate(); + ~MacPlatformDelegate() override; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MAC_MAC_PLATFORM_DELEGATE_H_ diff --git a/chromium/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm b/chromium/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm new file mode 100644 index 00000000000..e763e18bad5 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm @@ -0,0 +1,13 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/mac/mac_platform_delegate.h" + +namespace device_signals { + +MacPlatformDelegate::MacPlatformDelegate() = default; + +MacPlatformDelegate::~MacPlatformDelegate() = default; + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/mock_file_system_service.cc b/chromium/components/device_signals/core/system_signals/mock_file_system_service.cc new file mode 100644 index 00000000000..0285aa5e64a --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/mock_file_system_service.cc @@ -0,0 +1,12 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/mock_file_system_service.h" + +namespace device_signals { + +MockFileSystemService::MockFileSystemService() = default; +MockFileSystemService::~MockFileSystemService() = default; + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/mock_file_system_service.h b/chromium/components/device_signals/core/system_signals/mock_file_system_service.h new file mode 100644 index 00000000000..3b6badfc228 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/mock_file_system_service.h @@ -0,0 +1,32 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MOCK_FILE_SYSTEM_SERVICE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MOCK_FILE_SYSTEM_SERVICE_H_ + +#include "components/device_signals/core/system_signals/file_system_service.h" + +#include "components/device_signals/core/common/common_types.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace device_signals { + +class MockFileSystemService : public FileSystemService { + public: + MockFileSystemService(); + ~MockFileSystemService() override; + + MOCK_METHOD(std::vector<FileSystemItem>, + GetSignals, + (const std::vector<GetFileSystemInfoOptions>&), + (override)); + MOCK_METHOD(PresenceValue, + ResolveFileSystemItem, + (const base::FilePath&, base::FilePath*), + (const override)); +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MOCK_FILE_SYSTEM_SERVICE_H_ diff --git a/chromium/components/device_signals/core/system_signals/mock_platform_delegate.cc b/chromium/components/device_signals/core/system_signals/mock_platform_delegate.cc new file mode 100644 index 00000000000..c15adacec80 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/mock_platform_delegate.cc @@ -0,0 +1,12 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/mock_platform_delegate.h" + +namespace device_signals { + +MockPlatformDelegate::MockPlatformDelegate() = default; +MockPlatformDelegate::~MockPlatformDelegate() = default; + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/mock_platform_delegate.h b/chromium/components/device_signals/core/system_signals/mock_platform_delegate.h new file mode 100644 index 00000000000..a08ed546ad1 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/mock_platform_delegate.h @@ -0,0 +1,34 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MOCK_PLATFORM_DELEGATE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MOCK_PLATFORM_DELEGATE_H_ + +#include "base/files/file_path.h" +#include "components/device_signals/core/common/common_types.h" +#include "components/device_signals/core/system_signals/platform_delegate.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace device_signals { + +class MockPlatformDelegate : public PlatformDelegate { + public: + MockPlatformDelegate(); + ~MockPlatformDelegate() override; + + MOCK_METHOD(bool, PathIsReadable, (const base::FilePath&), (const override)); + MOCK_METHOD(bool, DirectoryExists, (const base::FilePath&), (const override)); + MOCK_METHOD(bool, + ResolveFilePath, + (const base::FilePath&, base::FilePath*), + (override)); + MOCK_METHOD(FilePathMap<ExecutableMetadata>, + GetAllExecutableMetadata, + (const FilePathSet&), + (override)); +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_MOCK_PLATFORM_DELEGATE_H_ diff --git a/chromium/components/device_signals/core/system_signals/platform_delegate.cc b/chromium/components/device_signals/core/system_signals/platform_delegate.cc new file mode 100644 index 00000000000..973ce713608 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/platform_delegate.cc @@ -0,0 +1,23 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/platform_delegate.h" + +#include "base/files/file_path.h" +#include "build/build_config.h" + +namespace device_signals { + +bool CustomFilePathComparator::operator()(const base::FilePath& a, + const base::FilePath& b) const { +#if BUILDFLAG(IS_LINUX) + // On Linux, the file system is case sensitive. + return a < b; +#else + // On Windows and Mac, the file system is case insensitive. + return base::FilePath::CompareLessIgnoreCase(a.value(), b.value()); +#endif +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/platform_delegate.h b/chromium/components/device_signals/core/system_signals/platform_delegate.h new file mode 100644 index 00000000000..3cc0ed15894 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/platform_delegate.h @@ -0,0 +1,52 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_PLATFORM_DELEGATE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_PLATFORM_DELEGATE_H_ + +#include "base/containers/flat_map.h" +#include "base/containers/flat_set.h" + +namespace base { +class FilePath; +} // namespace base + +namespace device_signals { + +struct ExecutableMetadata; + +struct CustomFilePathComparator { + bool operator()(const base::FilePath& a, const base::FilePath& b) const; +}; + +template <typename T> +using FilePathMap = base::flat_map<base::FilePath, T, CustomFilePathComparator>; + +using FilePathSet = base::flat_set<base::FilePath, CustomFilePathComparator>; + +// Interface whose derived types encapsulate OS-specific functionalities. +class PlatformDelegate { + public: + virtual ~PlatformDelegate() = default; + + // Wrapper functions around implementation in base/files/file_util.h to allow + // mocking in tests. + virtual bool PathIsReadable(const base::FilePath& file_path) const = 0; + virtual bool DirectoryExists(const base::FilePath& file_path) const = 0; + + // Resolves environment variables and relative markers in `file_path`, and + // returns the absolute path via `resolved_file_path`. Returns true if + // successful. For consistency on all platforms, this method will return false + // if no file system item resides at the end path. + virtual bool ResolveFilePath(const base::FilePath& file_path, + base::FilePath* resolved_file_path) = 0; + + // Collects and return executable metadata for all the files in `file_paths`. + virtual FilePathMap<ExecutableMetadata> GetAllExecutableMetadata( + const FilePathSet& file_paths) = 0; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_PLATFORM_DELEGATE_H_ diff --git a/chromium/components/device_signals/core/system_signals/platform_utils.h b/chromium/components/device_signals/core/system_signals/platform_utils.h new file mode 100644 index 00000000000..9152e54d0af --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/platform_utils.h @@ -0,0 +1,23 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_PLATFORM_UTILS_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_PLATFORM_UTILS_H_ + +#include "base/process/process_handle.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace base { +class FilePath; +} // namespace base + +namespace device_signals { + +// Returns the file path pointing to the executable file that spawned +// the given process `pid`. +absl::optional<base::FilePath> GetProcessExePath(base::ProcessId pid); + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_PLATFORM_UTILS_H_ diff --git a/chromium/components/device_signals/core/system_signals/posix/BUILD.gn b/chromium/components/device_signals/core/system_signals/posix/BUILD.gn new file mode 100644 index 00000000000..513f71a302f --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/posix/BUILD.gn @@ -0,0 +1,16 @@ +# Copyright 2022 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. + +static_library("posix") { + public = [ "posix_platform_delegate.h" ] + + sources = [ "posix_platform_delegate.cc" ] + + public_deps = [ + "//components/device_signals/core/common", + "//components/device_signals/core/system_signals", + ] + + deps = [ "//base" ] +} diff --git a/chromium/components/device_signals/core/system_signals/posix/platform_utils_posix.cc b/chromium/components/device_signals/core/system_signals/posix/platform_utils_posix.cc new file mode 100644 index 00000000000..e749bdb4fe9 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/posix/platform_utils_posix.cc @@ -0,0 +1,20 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/platform_utils.h" + +#include "base/files/file_path.h" +#include "base/process/process_handle.h" + +namespace device_signals { + +absl::optional<base::FilePath> GetProcessExePath(base::ProcessId pid) { + auto file_path = base::GetProcessExecutablePath(pid); + if (file_path.empty()) { + return absl::nullopt; + } + return file_path; +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/posix/posix_platform_delegate.cc b/chromium/components/device_signals/core/system_signals/posix/posix_platform_delegate.cc new file mode 100644 index 00000000000..0b22a56febe --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/posix/posix_platform_delegate.cc @@ -0,0 +1,30 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/posix/posix_platform_delegate.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "components/device_signals/core/common/common_types.h" + +namespace device_signals { + +PosixPlatformDelegate::PosixPlatformDelegate() = default; + +PosixPlatformDelegate::~PosixPlatformDelegate() = default; + +bool PosixPlatformDelegate::ResolveFilePath( + const base::FilePath& file_path, + base::FilePath* resolved_file_path) { + base::FilePath local_resolved_file_path = + base::MakeAbsoluteFilePath(file_path); + if (local_resolved_file_path.empty()) { + return false; + } + + *resolved_file_path = local_resolved_file_path; + return true; +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/posix/posix_platform_delegate.h b/chromium/components/device_signals/core/system_signals/posix/posix_platform_delegate.h new file mode 100644 index 00000000000..feca4766532 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/posix/posix_platform_delegate.h @@ -0,0 +1,26 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_POSIX_POSIX_PLATFORM_DELEGATE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_POSIX_POSIX_PLATFORM_DELEGATE_H_ + +#include "components/device_signals/core/system_signals/base_platform_delegate.h" + +namespace device_signals { + +class PosixPlatformDelegate : public BasePlatformDelegate { + public: + ~PosixPlatformDelegate() override; + + // PlatformDelegate: + bool ResolveFilePath(const base::FilePath& file_path, + base::FilePath* resolved_file_path) override; + + protected: + PosixPlatformDelegate(); +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_POSIX_POSIX_PLATFORM_DELEGATE_H_ diff --git a/chromium/components/device_signals/core/system_signals/win/BUILD.gn b/chromium/components/device_signals/core/system_signals/win/BUILD.gn new file mode 100644 index 00000000000..9c81e5b7d55 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/BUILD.gn @@ -0,0 +1,68 @@ +# Copyright 2022 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. + +static_library("win") { + public = [ + "win_platform_delegate.h", + "wmi_client.h", + "wmi_client_impl.h", + "wsc_client.h", + "wsc_client_impl.h", + ] + + sources = [ + "win_platform_delegate.cc", + "wmi_client.cc", + "wmi_client_impl.cc", + "wsc_client.cc", + "wsc_client_impl.cc", + ] + + public_deps = [ + "//base", + "//components/device_signals/core/common", + "//components/device_signals/core/common/win", + "//components/device_signals/core/system_signals", + "//third_party/abseil-cpp:absl", + ] +} + +source_set("test_support") { + testonly = true + sources = [ + "com_fakes.cc", + "com_fakes.h", + "mock_wmi_client.cc", + "mock_wmi_client.h", + "mock_wsc_client.cc", + "mock_wsc_client.h", + ] + + deps = [ + ":win", + "//base", + "//testing/gmock", + "//testing/gtest", + "//third_party/abseil-cpp:absl", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "win_platform_delegate_unittest.cc", + "wmi_client_impl_unittest.cc", + "wsc_client_impl_unittest.cc", + ] + + deps = [ + ":test_support", + ":win", + "//base", + "//base/test:test_support", + "//testing/gmock", + "//testing/gtest", + "//third_party/abseil-cpp:absl", + ] +} diff --git a/chromium/components/device_signals/core/common/win/com_fakes.cc b/chromium/components/device_signals/core/system_signals/win/com_fakes.cc index b9c74e9ad30..3d1c01b91be 100644 --- a/chromium/components/device_signals/core/common/win/com_fakes.cc +++ b/chromium/components/device_signals/core/system_signals/win/com_fakes.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "components/device_signals/core/common/win/com_fakes.h" +#include "components/device_signals/core/system_signals/win/com_fakes.h" #include "base/check.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/chromium/components/device_signals/core/common/win/com_fakes.h b/chromium/components/device_signals/core/system_signals/win/com_fakes.h index 06fc7314b75..5bbfd286142 100644 --- a/chromium/components/device_signals/core/common/win/com_fakes.h +++ b/chromium/components/device_signals/core/system_signals/win/com_fakes.h @@ -1,9 +1,9 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. -#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_COM_FAKES_H_ -#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_COM_FAKES_H_ +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_COM_FAKES_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_COM_FAKES_H_ #include <atlcomcli.h> #include <iwscapi.h> @@ -231,4 +231,4 @@ class FakeWSCProductList : public IWSCProductList { } // namespace device_signals -#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_COM_FAKES_H_ +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_COM_FAKES_H_ diff --git a/chromium/components/device_signals/core/common/win/mock_wmi_client.cc b/chromium/components/device_signals/core/system_signals/win/mock_wmi_client.cc index f12f35b0c60..f7f3729dad3 100644 --- a/chromium/components/device_signals/core/common/win/mock_wmi_client.cc +++ b/chromium/components/device_signals/core/system_signals/win/mock_wmi_client.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "components/device_signals/core/common/win/mock_wmi_client.h" +#include "components/device_signals/core/system_signals/win/mock_wmi_client.h" namespace device_signals { diff --git a/chromium/components/device_signals/core/system_signals/win/mock_wmi_client.h b/chromium/components/device_signals/core/system_signals/win/mock_wmi_client.h new file mode 100644 index 00000000000..500526e08c5 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/mock_wmi_client.h @@ -0,0 +1,24 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_MOCK_WMI_CLIENT_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_MOCK_WMI_CLIENT_H_ + +#include "components/device_signals/core/system_signals/win/wmi_client.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace device_signals { + +class MockWmiClient : public WmiClient { + public: + MockWmiClient(); + ~MockWmiClient() override; + + MOCK_METHOD(WmiAvProductsResponse, GetAntiVirusProducts, (), (override)); + MOCK_METHOD(WmiHotfixesResponse, GetInstalledHotfixes, (), (override)); +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_MOCK_WMI_CLIENT_H_ diff --git a/chromium/components/device_signals/core/common/win/mock_wsc_client.cc b/chromium/components/device_signals/core/system_signals/win/mock_wsc_client.cc index 18bb1698827..c0c75d4552e 100644 --- a/chromium/components/device_signals/core/common/win/mock_wsc_client.cc +++ b/chromium/components/device_signals/core/system_signals/win/mock_wsc_client.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "components/device_signals/core/common/win/mock_wsc_client.h" +#include "components/device_signals/core/system_signals/win/mock_wsc_client.h" namespace device_signals { diff --git a/chromium/components/device_signals/core/common/win/mock_wsc_client.h b/chromium/components/device_signals/core/system_signals/win/mock_wsc_client.h index 080dfa50fbe..77267617e07 100644 --- a/chromium/components/device_signals/core/common/win/mock_wsc_client.h +++ b/chromium/components/device_signals/core/system_signals/win/mock_wsc_client.h @@ -1,11 +1,11 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. -#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_MOCK_WSC_CLIENT_H_ -#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_MOCK_WSC_CLIENT_H_ +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_MOCK_WSC_CLIENT_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_MOCK_WSC_CLIENT_H_ -#include "components/device_signals/core/common/win/wsc_client.h" +#include "components/device_signals/core/system_signals/win/wsc_client.h" #include "testing/gmock/include/gmock/gmock.h" namespace device_signals { @@ -20,4 +20,4 @@ class MockWscClient : public WscClient { } // namespace device_signals -#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_MOCK_WMI_CLIENT_H_ +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_MOCK_WSC_CLIENT_H_ diff --git a/chromium/components/device_signals/core/system_signals/win/platform_utils_win.cc b/chromium/components/device_signals/core/system_signals/win/platform_utils_win.cc new file mode 100644 index 00000000000..c25fff49d85 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/platform_utils_win.cc @@ -0,0 +1,31 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/platform_utils.h" + +#include <windows.h> + +#include "base/files/file_path.h" +#include "base/process/process.h" + +namespace device_signals { + +absl::optional<base::FilePath> GetProcessExePath(base::ProcessId pid) { + base::Process process( + ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); + if (!process.IsValid()) { + return absl::nullopt; + } + + DWORD path_len = MAX_PATH; + wchar_t path_string[MAX_PATH]; + if (!::QueryFullProcessImageName(process.Handle(), 0, path_string, + &path_len)) { + return absl::nullopt; + } + + return base::FilePath(path_string); +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/win/win_platform_delegate.cc b/chromium/components/device_signals/core/system_signals/win/win_platform_delegate.cc new file mode 100644 index 00000000000..dcf1d119d58 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/win_platform_delegate.cc @@ -0,0 +1,65 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/win/win_platform_delegate.h" + +#include <windows.h> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/process/process.h" +#include "base/process/process_iterator.h" +#include "base/strings/string_util.h" +#include "base/strings/string_util_win.h" +#include "components/device_signals/core/common/common_types.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace device_signals { + +namespace { + +// Helper function for expanding all environment variables in `path`. +absl::optional<std::wstring> ExpandEnvironmentVariables( + const std::wstring& path) { + static const DWORD kMaxBuffer = 32 * 1024; // Max according to MSDN. + std::wstring path_expanded; + DWORD path_len = MAX_PATH; + do { + DWORD result = ::ExpandEnvironmentStrings( + path.c_str(), base::WriteInto(&path_expanded, path_len), path_len); + if (!result) { + // Failed to expand variables. + break; + } + if (result <= path_len) + return path_expanded.substr(0, result - 1); + path_len = result; + } while (path_len < kMaxBuffer); + + return absl::nullopt; +} + +} // namespace + +WinPlatformDelegate::WinPlatformDelegate() = default; + +WinPlatformDelegate::~WinPlatformDelegate() = default; + +bool WinPlatformDelegate::ResolveFilePath(const base::FilePath& file_path, + base::FilePath* resolved_file_path) { + auto expanded_path_wstring = ExpandEnvironmentVariables(file_path.value()); + if (!expanded_path_wstring) { + return false; + } + + auto expanded_file_path = base::FilePath(expanded_path_wstring.value()); + if (!base::PathExists(expanded_file_path)) { + return false; + } + + *resolved_file_path = base::MakeAbsoluteFilePath(expanded_file_path); + return true; +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/win/win_platform_delegate.h b/chromium/components/device_signals/core/system_signals/win/win_platform_delegate.h new file mode 100644 index 00000000000..b8e456a20c5 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/win_platform_delegate.h @@ -0,0 +1,24 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WIN_PLATFORM_DELEGATE_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WIN_PLATFORM_DELEGATE_H_ + +#include "components/device_signals/core/system_signals/base_platform_delegate.h" + +namespace device_signals { + +class WinPlatformDelegate : public BasePlatformDelegate { + public: + WinPlatformDelegate(); + ~WinPlatformDelegate() override; + + // PlatformDelegate: + bool ResolveFilePath(const base::FilePath& file_path, + base::FilePath* resolved_file_path) override; +}; + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WIN_PLATFORM_DELEGATE_H_ diff --git a/chromium/components/device_signals/core/system_signals/win/win_platform_delegate_unittest.cc b/chromium/components/device_signals/core/system_signals/win/win_platform_delegate_unittest.cc new file mode 100644 index 00000000000..8e6f82a075c --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/win_platform_delegate_unittest.cc @@ -0,0 +1,80 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/win/win_platform_delegate.h" + +#include <array> + +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/strings/strcat.h" +#include "base/strings/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device_signals { + +namespace { + +// Using regular strings instead of file path literals as they will be used +// to construct all sorts of file paths, and also non-file-paths. +constexpr char kEnvironmentVariableName[] = "TestEnvironmentVariablePath"; +constexpr char kTestFileName[] = "test_file"; + +constexpr base::FilePath::CharType kInexistantFileName[] = + FILE_PATH_LITERAL("does_not_exit"); + +} // namespace + +class WinPlatformDelegateTest : public testing::Test { + protected: + WinPlatformDelegateTest() : env_(base::Environment::Create()) { + EXPECT_TRUE(scoped_dir_.CreateUniqueTempDir()); + absolute_file_path_ = scoped_dir_.GetPath().Append( + base::FilePath::FromUTF8Unsafe(kTestFileName)); + EXPECT_TRUE( + base::WriteFile(absolute_file_path_, "irrelevant file content")); + + env_->SetVar(kEnvironmentVariableName, + scoped_dir_.GetPath().AsUTF8Unsafe()); + } + + ~WinPlatformDelegateTest() override { + env_->UnSetVar(kEnvironmentVariableName); + } + + base::ScopedTempDir scoped_dir_; + base::FilePath absolute_file_path_; + std::unique_ptr<base::Environment> env_; + WinPlatformDelegate platform_delegate_; +}; + +TEST_F(WinPlatformDelegateTest, ResolveFilePath_Success) { + std::string directory_name = scoped_dir_.GetPath().BaseName().AsUTF8Unsafe(); + + std::array<std::string, 4> test_cases = { + absolute_file_path_.AsUTF8Unsafe(), + base::StrCat({"%", kEnvironmentVariableName, "%\\", kTestFileName}), + base::StrCat({"%", kEnvironmentVariableName, "%\\..\\", directory_name, + "\\", kTestFileName}), + + // Should work with directories too. + scoped_dir_.GetPath().AsUTF8Unsafe()}; + + for (const auto& test_case : test_cases) { + base::FilePath resolved_fp; + EXPECT_TRUE(platform_delegate_.ResolveFilePath( + base::FilePath::FromUTF8Unsafe(test_case), &resolved_fp)); + } +} + +TEST_F(WinPlatformDelegateTest, ResolveFilePath_Fail) { + base::FilePath resolved_fp; + EXPECT_FALSE(platform_delegate_.ResolveFilePath( + scoped_dir_.GetPath().Append(kInexistantFileName), &resolved_fp)); + EXPECT_EQ(resolved_fp, base::FilePath()); +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/common/win/wmi_client.cc b/chromium/components/device_signals/core/system_signals/win/wmi_client.cc index 7aa439e8792..f460779d8dc 100644 --- a/chromium/components/device_signals/core/common/win/wmi_client.cc +++ b/chromium/components/device_signals/core/system_signals/win/wmi_client.cc @@ -1,11 +1,18 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "components/device_signals/core/common/win/wmi_client.h" +#include "components/device_signals/core/system_signals/win/wmi_client.h" namespace device_signals { +WmiAvProductsResponse::WmiAvProductsResponse() = default; + +WmiAvProductsResponse::WmiAvProductsResponse( + const WmiAvProductsResponse& other) = default; + +WmiAvProductsResponse::~WmiAvProductsResponse() = default; + WmiHotfixesResponse::WmiHotfixesResponse() = default; WmiHotfixesResponse::WmiHotfixesResponse(const WmiHotfixesResponse& other) = diff --git a/chromium/components/device_signals/core/common/win/wmi_client.h b/chromium/components/device_signals/core/system_signals/win/wmi_client.h index 6215633cf7a..58fb426093a 100644 --- a/chromium/components/device_signals/core/common/win/wmi_client.h +++ b/chromium/components/device_signals/core/system_signals/win/wmi_client.h @@ -1,9 +1,9 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. -#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WMI_CLIENT_H_ -#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WMI_CLIENT_H_ +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WMI_CLIENT_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WMI_CLIENT_H_ #include <string> #include <vector> @@ -17,10 +17,28 @@ namespace device_signals { // Errors that can occur when calling WMI, or parsing response values. +// Do not change ordering. This enum is captured as +// `DeviceSignalsWmiParsingError` in enums.xml. enum class WmiParsingError { kFailedToIterateResults = 0, kFailedToGetName = 1, - kMaxValue = kFailedToGetName + kFailedToGetState = 2, + kStateInvalid = 3, + kFailedToGetId = 4, + kMaxValue = kFailedToGetId +}; + +// Response object for calls to retrieve information about installed AntiVirus +// software. +struct WmiAvProductsResponse { + WmiAvProductsResponse(); + ~WmiAvProductsResponse(); + + WmiAvProductsResponse(const WmiAvProductsResponse& other); + + std::vector<AvProduct> av_products; + absl::optional<base::win::WmiError> query_error; + std::vector<WmiParsingError> parsing_errors; }; // Response object for calls to retrieve information about installed hotfix @@ -41,10 +59,13 @@ class WmiClient { public: virtual ~WmiClient() = default; + // Will retrieve information about installed AntiVirus software. + virtual WmiAvProductsResponse GetAntiVirusProducts() = 0; + // Will retrieve information about installed hotfix updates. virtual WmiHotfixesResponse GetInstalledHotfixes() = 0; }; } // namespace device_signals -#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WMI_CLIENT_H_ +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WMI_CLIENT_H_ diff --git a/chromium/components/device_signals/core/system_signals/win/wmi_client_impl.cc b/chromium/components/device_signals/core/system_signals/win/wmi_client_impl.cc new file mode 100644 index 00000000000..779558b84a4 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/wmi_client_impl.cc @@ -0,0 +1,201 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/win/wmi_client_impl.h" + +#include <wbemidl.h> +#include <windows.h> +#include <wrl/client.h> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/check.h" +#include "base/metrics/histogram_functions.h" +#include "base/strings/sys_string_conversions.h" +#include "base/threading/scoped_blocking_call.h" +#include "base/win/com_init_util.h" +#include "base/win/scoped_bstr.h" +#include "base/win/scoped_variant.h" +#include "base/win/windows_version.h" +#include "base/win/wmi.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +using Microsoft::WRL::ComPtr; + +namespace device_signals { + +namespace { + +// Parses a string value from `class_object` named `property_name`. +absl::optional<std::string> ParseString( + const std::wstring& property_name, + const ComPtr<IWbemClassObject>& class_object) { + base::win::ScopedVariant string_variant; + HRESULT hr = class_object->Get(property_name.c_str(), 0, + string_variant.Receive(), 0, 0); + + if (FAILED(hr) || string_variant.type() != VT_BSTR) { + return absl::nullopt; + } + + // Owned by ScopedVariant. + BSTR temp_bstr = V_BSTR(string_variant.ptr()); + return base::SysWideToUTF8( + std::wstring(temp_bstr, ::SysStringLen(temp_bstr))); +} + +} // namespace + +WmiClientImpl::WmiClientImpl() + : run_query_callback_(base::BindRepeating(base::win::RunWmiQuery)) {} + +WmiClientImpl::WmiClientImpl(RunWmiQueryCallback run_query_callback) + : run_query_callback_(std::move(run_query_callback)) {} + +WmiClientImpl::~WmiClientImpl() = default; + +WmiAvProductsResponse WmiClientImpl::GetAntiVirusProducts() { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::MAY_BLOCK); + ComPtr<IEnumWbemClassObject> enumerator; + auto error_code = + run_query_callback_.Run(base::win::kSecurityCenter2ServerName, + L"SELECT * FROM AntiVirusProduct", &enumerator); + + WmiAvProductsResponse response; + if (error_code.has_value()) { + response.query_error = error_code.value(); + return response; + } + + // Iterate over the results of the WMI query. Each result will be an + // AntiVirusProduct instance. + HRESULT hr; + while (true) { + ComPtr<IWbemClassObject> class_object; + ULONG items_returned = 0U; + hr = enumerator->Next(WBEM_INFINITE, 1, &class_object, &items_returned); + + if (hr == WBEM_S_FALSE || items_returned == 0) { + // Reached the end of the enumerator. + break; + } + + // Something went wrong and it wasn't the end of the enumerator. + if (FAILED(hr)) { + response.parsing_errors.push_back( + WmiParsingError::kFailedToIterateResults); + continue; + } + + base::win::ScopedVariant product_state; + hr = class_object->Get(L"productState", 0, product_state.Receive(), 0, 0); + + if (FAILED(hr) || product_state.type() != VT_I4) { + response.parsing_errors.push_back(WmiParsingError::kFailedToGetState); + continue; + } + + LONG state_val = V_I4(product_state.ptr()); + internal::PRODUCT_STATE product_state_struct; + std::copy(reinterpret_cast<const char*>(&state_val), + reinterpret_cast<const char*>(&state_val) + sizeof state_val, + reinterpret_cast<char*>(&product_state_struct)); + // Map the values from product_state_struct to the av struct values. + AvProduct av_product; + switch (product_state_struct.security_state) { + case 0: + av_product.state = AvProductState::kOff; + break; + case 1: + av_product.state = AvProductState::kOn; + break; + case 2: + av_product.state = AvProductState::kSnoozed; + break; + default: + // Unknown state. + response.parsing_errors.push_back(WmiParsingError::kStateInvalid); + continue; + } + + absl::optional<std::string> display_name = + ParseString(L"displayName", class_object); + if (!display_name.has_value()) { + response.parsing_errors.push_back(WmiParsingError::kFailedToGetName); + continue; + } + + av_product.display_name = display_name.value(); + + absl::optional<std::string> product_id = + ParseString(L"instanceGuid", class_object); + if (!product_id.has_value()) { + response.parsing_errors.push_back(WmiParsingError::kFailedToGetId); + continue; + } + + av_product.product_id = product_id.value(); + + // If all values were parsed properly, add `av_product` into the response + // vector. If any value could not be parsed properly, the item was + // discarded. + response.av_products.push_back(av_product); + } + + return response; +} + +WmiHotfixesResponse WmiClientImpl::GetInstalledHotfixes() { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::MAY_BLOCK); + ComPtr<IEnumWbemClassObject> enumerator; + auto error_code = run_query_callback_.Run( + base::win::kCimV2ServerName, L"SELECT * FROM Win32_QuickFixEngineering", + &enumerator); + + WmiHotfixesResponse response; + if (error_code.has_value()) { + response.query_error = error_code.value(); + return response; + } + + HRESULT hr; + while (true) { + ComPtr<IWbemClassObject> class_object; + ULONG items_returned = 0U; + hr = enumerator->Next(WBEM_INFINITE, 1, &class_object, &items_returned); + + if (hr == WBEM_S_FALSE || items_returned == 0U) { + // Reached the end of the enumerator. + break; + } + + // Something went wrong, and it wasn't the end of the enumerator. + if (FAILED(hr)) { + response.parsing_errors.push_back( + WmiParsingError::kFailedToIterateResults); + continue; + } + + absl::optional<std::string> hotfix_id = + ParseString(L"HotFixId", class_object); + if (!hotfix_id.has_value()) { + response.parsing_errors.push_back(WmiParsingError::kFailedToGetName); + continue; + } + + InstalledHotfix hotfix; + hotfix.hotfix_id = hotfix_id.value(); + + // If all values were parsed properly, add `hotfix` into the response + // vector. If any value could not be parsed properly, the item was + // discarded. + response.hotfixes.push_back(hotfix); + } + + return response; +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/system_signals/win/wmi_client_impl.h b/chromium/components/device_signals/core/system_signals/win/wmi_client_impl.h new file mode 100644 index 00000000000..49cb8f8a9a3 --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/wmi_client_impl.h @@ -0,0 +1,64 @@ +// Copyright 2022 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. + +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WMI_CLIENT_IMPL_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WMI_CLIENT_IMPL_H_ + +#include "base/callback.h" +#include "components/device_signals/core/system_signals/win/wmi_client.h" + +// WMI interfaces are available on Windows Vista and above, and are officially +// undocumented. +namespace device_signals { + +class WmiClientImpl : public WmiClient { + public: + using RunWmiQueryCallback = + base::RepeatingCallback<absl::optional<base::win::WmiError>( + const std::wstring&, + const std::wstring&, + Microsoft::WRL::ComPtr<IEnumWbemClassObject>*)>; + + WmiClientImpl(); + + ~WmiClientImpl() override; + + // WmiClient: + WmiAvProductsResponse GetAntiVirusProducts() override; + WmiHotfixesResponse GetInstalledHotfixes() override; + + private: + friend class WmiClientImplTest; + + // Constructor taking in a `run_query_callback` which can be used to mock + // running the WMI query. + explicit WmiClientImpl(RunWmiQueryCallback run_query_callback); + + RunWmiQueryCallback run_query_callback_; +}; + +// Type shared in an internal namespace to allow for reuse in unit tests without +// duplication. +namespace internal { +// This is an undocumented structure returned from querying the "productState" +// uint32 from the AntiVirusProduct in WMI. +// http://neophob.com/2010/03/wmi-query-windows-securitycenter2/ gives a good +// summary and testing was also done with a variety of AV products to determine +// these values as accurately as possible. +#pragma pack(push) +#pragma pack(1) +struct PRODUCT_STATE { + uint8_t unknown_1 : 4; + uint8_t definition_state : 4; // 1 = Out of date, 0 = Up to date. + uint8_t unknown_2 : 4; + uint8_t security_state : 4; // 0 = Inactive, 1 = Active, 2 = Snoozed. + uint8_t security_provider; // matches WSC_SECURITY_PROVIDER in wscapi.h. + uint8_t unknown_3; +}; +#pragma pack(pop) +} // namespace internal + +} // namespace device_signals + +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WMI_CLIENT_IMPL_H_ diff --git a/chromium/components/device_signals/core/system_signals/win/wmi_client_impl_unittest.cc b/chromium/components/device_signals/core/system_signals/win/wmi_client_impl_unittest.cc new file mode 100644 index 00000000000..5331a49211b --- /dev/null +++ b/chromium/components/device_signals/core/system_signals/win/wmi_client_impl_unittest.cc @@ -0,0 +1,263 @@ +// Copyright 2022 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 "components/device_signals/core/system_signals/win/wmi_client_impl.h" + +#include <wbemidl.h> +#include <windows.h> +#include <wrl/client.h> +#include <wrl/implements.h> +#include <memory> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/strings/sys_string_conversions.h" +#include "base/test/task_environment.h" +#include "base/win/wmi.h" +#include "components/device_signals/core/common/win/win_types.h" +#include "components/device_signals/core/system_signals/win/com_fakes.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +using Microsoft::WRL::ComPtr; + +namespace device_signals { + +namespace { + +constexpr uint8_t kOffAvState = 0; +constexpr uint8_t kOnAvState = 1; +constexpr uint8_t kSnoozedAvState = 2; +constexpr uint8_t kUnknownAvState = 3; + +FakeWbemClassObject CreateHotfixObject(const std::wstring& hotfix_id) { + FakeWbemClassObject hotfix_obj; + hotfix_obj.Set(L"HotFixId", hotfix_id); + return hotfix_obj; +} + +FakeWbemClassObject CreateAvObject(const std::wstring& display_name, + const std::wstring& product_id, + uint8_t state) { + FakeWbemClassObject av_obj; + av_obj.Set(L"displayName", display_name); + av_obj.Set(L"instanceGuid", product_id); + + internal::PRODUCT_STATE product_state; + product_state.security_state = state; + + LONG state_val; + std::copy( + reinterpret_cast<const char*>(&product_state), + reinterpret_cast<const char*>(&product_state) + sizeof product_state, + reinterpret_cast<char*>(&state_val)); + av_obj.Set(L"productState", state_val); + + return av_obj; +} + +} // namespace + +class WmiClientImplTest : public testing::Test { + public: + WmiClientImplTest() + : wmi_client_(base::BindRepeating(&WmiClientImplTest::RunQuery, + base::Unretained(this))) {} + + protected: + absl::optional<base::win::WmiError> RunQuery( + const std::wstring& server_name, + const std::wstring& query, + ComPtr<IEnumWbemClassObject>* enumerator) { + ++nb_calls_; + captured_server_name_ = server_name; + captured_query_ = query; + + if (query_error_.has_value()) { + return query_error_.value(); + } + *enumerator = &fake_enumerator_; + return absl::nullopt; + } + + void ExpectAvQueryRan() { + EXPECT_EQ(captured_server_name_, base::win::kSecurityCenter2ServerName); + EXPECT_EQ(captured_query_, L"SELECT * FROM AntiVirusProduct"); + EXPECT_EQ(nb_calls_, 1U); + } + + void ExpectHotfixQueryRan() { + EXPECT_EQ(captured_server_name_, base::win::kCimV2ServerName); + EXPECT_EQ(captured_query_, L"SELECT * FROM Win32_QuickFixEngineering"); + EXPECT_EQ(nb_calls_, 1U); + } + + FakeEnumWbemClassObject fake_enumerator_; + absl::optional<base::win::WmiError> query_error_; + + std::wstring captured_server_name_; + std::wstring captured_query_; + uint32_t nb_calls_ = 0; + + WmiClientImpl wmi_client_; +}; + +// Tests how the client behaves when the WMI query fails when querying for AV +// products. +TEST_F(WmiClientImplTest, GetAntiVirusProducts_FailedRunQuery) { + query_error_ = base::win::WmiError::kFailedToConnectToWMI; + + auto av_response = wmi_client_.GetAntiVirusProducts(); + + ExpectAvQueryRan(); + EXPECT_EQ(av_response.av_products.size(), 0U); + EXPECT_EQ(av_response.parsing_errors.size(), 0U); + EXPECT_EQ(av_response.query_error, + base::win::WmiError::kFailedToConnectToWMI); +} + +// Tests how the client behaves when iterating through objects with all known +// AV product states. +TEST_F(WmiClientImplTest, GetAntiVirusProducts_AllProductStates) { + std::wstring display_name1 = L"Av Display Name 1"; + std::wstring product_id1 = L"product ID 1"; + FakeWbemClassObject av_obj1 = + CreateAvObject(display_name1, product_id1, kOffAvState); + + std::wstring display_name2 = L"Av Display Name 2"; + std::wstring product_id2 = L"product ID 2"; + FakeWbemClassObject av_obj2 = + CreateAvObject(display_name2, product_id2, kOnAvState); + + std::wstring display_name3 = L"Av Display Name 3"; + std::wstring product_id3 = L"product ID 3"; + FakeWbemClassObject av_obj3 = + CreateAvObject(display_name3, product_id3, kSnoozedAvState); + + std::wstring display_name4 = L"Av Display Name 4"; + std::wstring product_id4 = L"product ID 4"; + FakeWbemClassObject av_obj4 = + CreateAvObject(display_name3, product_id4, kUnknownAvState); + + fake_enumerator_.Add(&av_obj1); + fake_enumerator_.Add(&av_obj2); + fake_enumerator_.Add(&av_obj3); + fake_enumerator_.Add(&av_obj4); + + auto av_response = wmi_client_.GetAntiVirusProducts(); + + ExpectAvQueryRan(); + EXPECT_EQ(av_response.query_error, absl::nullopt); + + // Known states were parsed successfully. + EXPECT_EQ(av_response.av_products.size(), 3U); + EXPECT_EQ(av_response.av_products[0].display_name, + base::SysWideToUTF8(display_name1)); + EXPECT_EQ(av_response.av_products[0].product_id, + base::SysWideToUTF8(product_id1)); + EXPECT_EQ(av_response.av_products[0].state, AvProductState::kOff); + EXPECT_EQ(av_response.av_products[1].display_name, + base::SysWideToUTF8(display_name2)); + EXPECT_EQ(av_response.av_products[1].product_id, + base::SysWideToUTF8(product_id2)); + EXPECT_EQ(av_response.av_products[1].state, AvProductState::kOn); + EXPECT_EQ(av_response.av_products[2].display_name, + base::SysWideToUTF8(display_name3)); + EXPECT_EQ(av_response.av_products[2].product_id, + base::SysWideToUTF8(product_id3)); + EXPECT_EQ(av_response.av_products[2].state, AvProductState::kSnoozed); + + // Unknown state is returned as parsing error. + ASSERT_EQ(av_response.parsing_errors.size(), 1U); + EXPECT_EQ(av_response.parsing_errors[0], WmiParsingError::kStateInvalid); +} + +// Tests how the client behaves when parsing an AV object which is missing the +// displayName value. +TEST_F(WmiClientImplTest, GetAntiVirusProducts_MissingDisplayName) { + FakeWbemClassObject av_obj1 = + CreateAvObject(L"Av Display Name", L"product ID", kOffAvState); + + av_obj1.Delete(L"displayName"); + + fake_enumerator_.Add(&av_obj1); + + auto av_response = wmi_client_.GetAntiVirusProducts(); + + ExpectAvQueryRan(); + EXPECT_EQ(av_response.query_error, absl::nullopt); + EXPECT_EQ(av_response.av_products.size(), 0U); + + // Missing name property is returned as parsing error. + ASSERT_EQ(av_response.parsing_errors.size(), 1U); + EXPECT_EQ(av_response.parsing_errors[0], WmiParsingError::kFailedToGetName); +} + +// Tests how the client behaves when parsing an AV object which is missing the +// displayName value. +TEST_F(WmiClientImplTest, GetAntiVirusProducts_MissingProductId) { + FakeWbemClassObject av_obj1 = + CreateAvObject(L"Av Display Name", L"product ID", kOffAvState); + + av_obj1.Delete(L"instanceGuid"); + + fake_enumerator_.Add(&av_obj1); + + auto av_response = wmi_client_.GetAntiVirusProducts(); + + ExpectAvQueryRan(); + EXPECT_EQ(av_response.query_error, absl::nullopt); + EXPECT_EQ(av_response.av_products.size(), 0U); + + // Missing ID property is returned as parsing error. + ASSERT_EQ(av_response.parsing_errors.size(), 1U); + EXPECT_EQ(av_response.parsing_errors[0], WmiParsingError::kFailedToGetId); +} + +// Tests how the client behaves when the WMI query fails when querying for +// installed hotfixes. +TEST_F(WmiClientImplTest, GetInstalledHotfixes_FailedRunQuery) { + query_error_ = base::win::WmiError::kFailedToConnectToWMI; + + auto hotfix_response = wmi_client_.GetInstalledHotfixes(); + + ExpectHotfixQueryRan(); + EXPECT_EQ(hotfix_response.hotfixes.size(), 0U); + EXPECT_EQ(hotfix_response.parsing_errors.size(), 0U); + EXPECT_EQ(hotfix_response.query_error, + base::win::WmiError::kFailedToConnectToWMI); +} + +// Tests how the client behaves when parsing hotfix objects, one of which is +// valid, and another which is missing an ID. +TEST_F(WmiClientImplTest, GetInstalledHotfixes_ParsingItems) { + std::wstring hotfix_id1 = L"some_hotfix_id"; + FakeWbemClassObject hotfix_obj1 = CreateHotfixObject(hotfix_id1); + + FakeWbemClassObject hotfix_obj2 = CreateHotfixObject(L"some_other_id"); + + hotfix_obj2.Delete(L"HotFixId"); + + fake_enumerator_.Add(&hotfix_obj1); + fake_enumerator_.Add(&hotfix_obj2); + + auto hotfix_response = wmi_client_.GetInstalledHotfixes(); + + ExpectHotfixQueryRan(); + EXPECT_EQ(hotfix_response.query_error, absl::nullopt); + + // Success item. + ASSERT_EQ(hotfix_response.hotfixes.size(), 1U); + EXPECT_EQ(hotfix_response.hotfixes[0].hotfix_id, + base::SysWideToUTF8(hotfix_id1)); + + // Failed item. + ASSERT_EQ(hotfix_response.parsing_errors.size(), 1U); + EXPECT_EQ(hotfix_response.parsing_errors[0], + WmiParsingError::kFailedToGetName); +} + +} // namespace device_signals diff --git a/chromium/components/device_signals/core/common/win/wsc_client.cc b/chromium/components/device_signals/core/system_signals/win/wsc_client.cc index 4b44c206ef8..7a8d80c4aee 100644 --- a/chromium/components/device_signals/core/common/win/wsc_client.cc +++ b/chromium/components/device_signals/core/system_signals/win/wsc_client.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "components/device_signals/core/common/win/wsc_client.h" +#include "components/device_signals/core/system_signals/win/wsc_client.h" namespace device_signals { diff --git a/chromium/components/device_signals/core/common/win/wsc_client.h b/chromium/components/device_signals/core/system_signals/win/wsc_client.h index 089808eee8d..f90390a72c5 100644 --- a/chromium/components/device_signals/core/common/win/wsc_client.h +++ b/chromium/components/device_signals/core/system_signals/win/wsc_client.h @@ -1,9 +1,9 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. -#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_H_ -#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_H_ +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WSC_CLIENT_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WSC_CLIENT_H_ #include <string> #include <vector> @@ -15,13 +15,19 @@ // https://docs.microsoft.com/en-us/windows/win32/api/iwscapi/ namespace device_signals { +// Errors that can occur when querying WSC. +// Do not change ordering. This enum is captured as +// `DeviceSignalsWscQueryError` in enums.xml. enum class WscQueryError { kFailedToCreateInstance = 0, kFailedToInitializeProductList = 1, kFailedToGetProductCount = 2, + kMaxValue = kFailedToGetProductCount }; // Errors that can occur when calling WSC, or parsing response values. +// Do not change ordering. This enum is captured as +// `DeviceSignalsWscParsingError` in enums.xml. enum class WscParsingError { kFailedToGetItem = 0, kFailedToGetState = 1, @@ -56,4 +62,4 @@ class WscClient { } // namespace device_signals -#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_H_ +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WSC_CLIENT_H_ diff --git a/chromium/components/device_signals/core/common/win/wsc_client_impl.cc b/chromium/components/device_signals/core/system_signals/win/wsc_client_impl.cc index fae2aed1514..3dfd5ecb04c 100644 --- a/chromium/components/device_signals/core/common/win/wsc_client_impl.cc +++ b/chromium/components/device_signals/core/system_signals/win/wsc_client_impl.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "components/device_signals/core/common/win/wsc_client_impl.h" +#include "components/device_signals/core/system_signals/win/wsc_client_impl.h" #include <iwscapi.h> #include <windows.h> @@ -40,9 +40,17 @@ AvProductState ConvertState(WSC_SECURITY_PRODUCT_STATE state) { } } -HRESULT CreateProductList(IWSCProductList** product_list) { - return ::CoCreateInstance(__uuidof(WSCProductList), nullptr, - CLSCTX_INPROC_SERVER, IID_PPV_ARGS(product_list)); +HRESULT CreateProductList(ComPtr<IWSCProductList>* out_product_list) { + ComPtr<IWSCProductList> product_list; + HRESULT hr = + ::CoCreateInstance(__uuidof(WSCProductList), nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&product_list)); + + if (!FAILED(hr)) { + *out_product_list = product_list; + } + + return hr; } } // namespace diff --git a/chromium/components/device_signals/core/common/win/wsc_client_impl.h b/chromium/components/device_signals/core/system_signals/win/wsc_client_impl.h index e17df9d36ef..3d32ae791c6 100644 --- a/chromium/components/device_signals/core/common/win/wsc_client_impl.h +++ b/chromium/components/device_signals/core/system_signals/win/wsc_client_impl.h @@ -1,21 +1,21 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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. -#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_IMPL_H_ -#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_IMPL_H_ +#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WSC_CLIENT_IMPL_H_ +#define COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WSC_CLIENT_IMPL_H_ #include <iwscapi.h> #include "base/callback.h" -#include "components/device_signals/core/common/win/wsc_client.h" +#include "components/device_signals/core/system_signals/win/wsc_client.h" namespace device_signals { class WscClientImpl : public WscClient { public: - using CreateProductListCallback = - base::RepeatingCallback<HRESULT(IWSCProductList**)>; + using CreateProductListCallback = base::RepeatingCallback<HRESULT( + Microsoft::WRL::ComPtr<IWSCProductList>*)>; WscClientImpl(); @@ -36,4 +36,4 @@ class WscClientImpl : public WscClient { } // namespace device_signals -#endif // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_IMPL_H_ +#endif // COMPONENTS_DEVICE_SIGNALS_CORE_SYSTEM_SIGNALS_WIN_WSC_CLIENT_IMPL_H_ diff --git a/chromium/components/device_signals/core/common/win/wsc_client_impl_unittest.cc b/chromium/components/device_signals/core/system_signals/win/wsc_client_impl_unittest.cc index e922328ad36..ae56cf38985 100644 --- a/chromium/components/device_signals/core/common/win/wsc_client_impl_unittest.cc +++ b/chromium/components/device_signals/core/system_signals/win/wsc_client_impl_unittest.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "components/device_signals/core/common/win/wsc_client_impl.h" +#include "components/device_signals/core/system_signals/win/wsc_client_impl.h" #include <iwscapi.h> #include <windows.h> @@ -15,9 +15,10 @@ #include "base/callback.h" #include "base/strings/sys_string_conversions.h" #include "base/test/task_environment.h" +#include "base/win/scoped_com_initializer.h" #include "base/win/windows_version.h" -#include "components/device_signals/core/common/win/com_fakes.h" #include "components/device_signals/core/common/win/win_types.h" +#include "components/device_signals/core/system_signals/win/com_fakes.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -39,7 +40,7 @@ class WscClientImplTest : public testing::Test { : wsc_client_(base::BindRepeating(&WscClientImplTest::CreateProductList, base::Unretained(this))) {} - HRESULT CreateProductList(IWSCProductList** product_list) { + HRESULT CreateProductList(ComPtr<IWSCProductList>* product_list) { if (fail_list_creation_) { return E_FAIL; } @@ -223,6 +224,8 @@ TEST_F(WscClientImplTest, GetAntiVirusProducts_ProductErrors) { // Smoke/sanity test to verify that Defender's instance GUID does not change // over time. This test actually calls WSC. TEST(RealWscClientImplTest, SmokeWsc_GetAntiVirusProducts) { + base::win::ScopedCOMInitializer scoped_com_initializer; + // That part of the display name is not translated when getting it from WSC, // so it can be used quite simply. constexpr char kPartialDefenderName[] = "Microsoft Defender"; |