| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/credential_provider/extension/app_inventory_manager.h" |
| |
| #include <memory> |
| |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/credential_provider/common/gcp_strings.h" |
| #include "chrome/credential_provider/gaiacp/gcp_utils.h" |
| #include "chrome/credential_provider/gaiacp/gcpw_strings.h" |
| #include "chrome/credential_provider/gaiacp/logging.h" |
| #include "chrome/credential_provider/gaiacp/mdm_utils.h" |
| #include "chrome/credential_provider/gaiacp/os_user_manager.h" |
| #include "chrome/credential_provider/gaiacp/reg_utils.h" |
| #include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h" |
| |
| namespace credential_provider { |
| |
| const base::TimeDelta kDefaultUploadAppInventoryRequestTimeout = |
| base::Milliseconds(12000); |
| |
| namespace { |
| |
| // Constants used for contacting the gem service. |
| const char kGemServiceUploadAppInventoryPath[] = "/v1/uploadDeviceDetails"; |
| const char kUploadAppInventoryRequestUserSidParameterName[] = "user_sid"; |
| const char kUploadAppInventoryRequestDeviceResourceIdParameterName[] = |
| "device_resource_id"; |
| const char kUploadAppInventoryRequestWin32AppsParameterName[] = |
| "windows_gpcw_app_info"; |
| const char kDmToken[] = "dm_token"; |
| const char kObfuscatedGaiaId[] = "obfuscated_gaia_id"; |
| const char kAppDisplayName[] = "name"; |
| const char kAppDisplayVersion[] = "version"; |
| const char kAppPublisher[] = "publisher"; |
| const char kAppType[] = "app_type"; |
| |
| const wchar_t kInstalledWin32AppsRegistryPath[] = |
| L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; |
| const wchar_t kInstalledWin32AppsRegistryPathWOW6432[] = |
| L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; |
| const wchar_t kDelimiter[] = L"\\"; |
| const wchar_t kAppDisplayNameRegistryKey[] = L"DisplayName"; |
| const wchar_t kAppDisplayVersionRegistryKey[] = L"DisplayVersion"; |
| const wchar_t kAppPublisherRegistryKey[] = L"Publisher"; |
| |
| // Registry key to control whether upload app data from ESA feature is |
| // enabled. |
| const wchar_t kUploadAppInventoryFromEsaEnabledRegKey[] = |
| L"upload_app_inventory_from_esa"; |
| |
| // The period of uploading app inventory to the backend. |
| const base::TimeDelta kUploadAppInventoryExecutionPeriod = base::Hours(3); |
| |
| // True when upload device details from ESA feature is enabled. |
| bool g_upload_app_inventory_from_esa_enabled = false; |
| |
| // Maximum number of retries if a HTTP call to the backend fails. |
| constexpr unsigned int kMaxNumHttpRetries = 3; |
| |
| // Defines a task that is called by the ESA to upload app data. |
| class UploadAppInventoryTask : public extension::Task { |
| public: |
| static std::unique_ptr<extension::Task> Create() { |
| std::unique_ptr<extension::Task> esa_task(new UploadAppInventoryTask()); |
| return esa_task; |
| } |
| |
| // ESA calls this to retrieve a configuration for the task execution. Return |
| // a default config for now. |
| extension::Config GetConfig() final { |
| extension::Config config; |
| config.execution_period = kUploadAppInventoryExecutionPeriod; |
| return config; |
| } |
| |
| // ESA calls this to set all the user-device contexts for the execution of the |
| // task. |
| HRESULT SetContext(const std::vector<extension::UserDeviceContext>& c) final { |
| context_ = c; |
| return S_OK; |
| } |
| |
| // ESA calls execute function to perform the actual task. |
| HRESULT Execute() final { |
| HRESULT task_status = S_OK; |
| for (const auto& c : context_) { |
| HRESULT hr = AppInventoryManager::Get()->UploadAppInventory(c); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "Failed uploading device details for " << c.user_sid |
| << ". hr=" << putHR(hr); |
| task_status = hr; |
| } |
| } |
| return task_status; |
| } |
| |
| private: |
| std::vector<extension::UserDeviceContext> context_; |
| }; |
| } // namespace |
| |
| // static |
| AppInventoryManager* AppInventoryManager::Get() { |
| return *GetInstanceStorage(); |
| } |
| |
| // static |
| AppInventoryManager** AppInventoryManager::GetInstanceStorage() { |
| static AppInventoryManager instance(kDefaultUploadAppInventoryRequestTimeout); |
| static AppInventoryManager* instance_storage = &instance; |
| return &instance_storage; |
| } |
| |
| // static |
| extension::TaskCreator AppInventoryManager::UploadAppInventoryTaskCreator() { |
| return base::BindRepeating(&UploadAppInventoryTask::Create); |
| } |
| |
| AppInventoryManager::AppInventoryManager( |
| base::TimeDelta upload_app_inventory_request_timeout) |
| : upload_app_inventory_request_timeout_( |
| upload_app_inventory_request_timeout) { |
| g_upload_app_inventory_from_esa_enabled = |
| GetGlobalFlagOrDefault(kUploadAppInventoryFromEsaEnabledRegKey, 1) == 1; |
| } |
| |
| AppInventoryManager::~AppInventoryManager() = default; |
| |
| GURL AppInventoryManager::GetGemServiceUploadAppInventoryUrl() { |
| GURL gem_service_url = GetGcpwServiceUrl(); |
| return gem_service_url.Resolve(kGemServiceUploadAppInventoryPath); |
| } |
| |
| bool AppInventoryManager::UploadAppInventoryFromEsaFeatureEnabled() const { |
| return g_upload_app_inventory_from_esa_enabled; |
| } |
| |
| // Uploads the app data into GEM database using |dm_token| |
| // for authentication and authorization. The GEM service would use |
| // |resource_id| for identifying the device entry in GEM database. |
| HRESULT AppInventoryManager::UploadAppInventory( |
| const extension::UserDeviceContext& context) { |
| std::wstring obfuscated_user_id; |
| HRESULT status = GetIdFromSid(context.user_sid.c_str(), &obfuscated_user_id); |
| if (FAILED(status)) { |
| LOGFN(ERROR) << "Could not get user id from sid " << context.user_sid; |
| return status; |
| } |
| |
| if (obfuscated_user_id.empty()) { |
| LOGFN(ERROR) << "Got empty user id from sid " << context.user_sid; |
| return E_FAIL; |
| } |
| |
| std::wstring dm_token_value = context.dm_token; |
| HRESULT hr; |
| if (dm_token_value.empty()) { |
| hr = GetGCPWDmToken(context.user_sid, &dm_token_value); |
| if (FAILED(hr)) { |
| LOGFN(WARNING) << "Failed to fetch DmToken hr=" << putHR(hr); |
| return hr; |
| } |
| } |
| |
| request_dict_ = std::make_unique<base::Value>(base::Value::Type::DICTIONARY); |
| request_dict_->SetStringKey(kUploadAppInventoryRequestUserSidParameterName, |
| base::WideToUTF8(context.user_sid)); |
| request_dict_->SetStringKey(kDmToken, base::WideToUTF8(dm_token_value)); |
| request_dict_->SetStringKey(kObfuscatedGaiaId, |
| base::WideToUTF8(obfuscated_user_id)); |
| std::wstring known_resource_id = |
| context.device_resource_id.empty() |
| ? GetUserDeviceResourceId(context.user_sid) |
| : context.device_resource_id; |
| // ResourceId cannot be empty while uploading app data. App data is updated |
| // only for devices with existing device record. |
| if (known_resource_id.empty()) { |
| LOGFN(ERROR) << "Could not find valid resourceId for sid:" |
| << context.user_sid; |
| return E_FAIL; |
| } |
| request_dict_->SetStringKey( |
| kUploadAppInventoryRequestDeviceResourceIdParameterName, |
| base::WideToUTF8(known_resource_id)); |
| |
| request_dict_->SetKey(kUploadAppInventoryRequestWin32AppsParameterName, |
| GetInstalledWin32Apps()); |
| |
| absl::optional<base::Value> request_result; |
| hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService( |
| AppInventoryManager::Get()->GetGemServiceUploadAppInventoryUrl(), |
| /* access_token= */ std::string(), {}, *request_dict_, |
| upload_app_inventory_request_timeout_, kMaxNumHttpRetries, |
| &request_result); |
| |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "BuildRequestAndFetchResultFromHttpService hr=" |
| << putHR(hr); |
| return E_FAIL; |
| } |
| return hr; |
| } |
| |
| base::Value AppInventoryManager::GetInstalledWin32Apps() { |
| std::vector<std::wstring> app_name_list; |
| std::vector<std::wstring> app_path_list; |
| |
| GetChildrenAtPath(kInstalledWin32AppsRegistryPath, app_name_list); |
| for (std::wstring a : app_name_list) { |
| app_path_list.push_back(std::wstring(kInstalledWin32AppsRegistryPath) |
| .append(std::wstring(kDelimiter)) |
| .append(a)); |
| } |
| app_name_list.clear(); |
| GetChildrenAtPath(kInstalledWin32AppsRegistryPathWOW6432, app_name_list); |
| for (std::wstring a : app_name_list) { |
| app_path_list.push_back(std::wstring(kInstalledWin32AppsRegistryPathWOW6432) |
| .append(std::wstring(kDelimiter)) |
| .append(a)); |
| } |
| |
| base::Value app_info_value_list(base::Value::Type::LIST); |
| for (std::wstring regPath : app_path_list) { |
| auto request_dict = |
| std::make_unique<base::Value>(base::Value::Type::DICTIONARY); |
| |
| wchar_t display_name[256]; |
| ULONG display_length = std::size(display_name); |
| HRESULT hr = |
| GetMachineRegString(regPath, std::wstring(kAppDisplayNameRegistryKey), |
| display_name, &display_length); |
| if (hr == S_OK) { |
| request_dict->SetStringKey(kAppDisplayName, |
| base::WideToUTF8(display_name)); |
| |
| wchar_t display_version[256]; |
| ULONG version_length = std::size(display_version); |
| hr = GetMachineRegString(regPath, |
| std::wstring(kAppDisplayVersionRegistryKey), |
| display_version, &version_length); |
| if (hr == S_OK) { |
| request_dict->SetStringKey(kAppDisplayVersion, |
| base::WideToUTF8(display_version)); |
| } |
| |
| wchar_t publisher[256]; |
| ULONG publisher_length = std::size(publisher); |
| hr = GetMachineRegString(regPath, std::wstring(kAppPublisherRegistryKey), |
| publisher, &publisher_length); |
| if (hr == S_OK) { |
| request_dict->SetStringKey(kAppPublisher, base::WideToUTF8(publisher)); |
| } |
| |
| // App_type value 1 refers to WIN_32 applications. |
| request_dict->SetIntKey(kAppType, 1); |
| |
| app_info_value_list.Append( |
| base::Value::FromUniquePtrValue(std::move(request_dict))); |
| } |
| } |
| |
| return app_info_value_list; |
| } |
| |
| void AppInventoryManager::SetUploadAppInventoryFromEsaFeatureEnabledForTesting( |
| bool value) { |
| g_upload_app_inventory_from_esa_enabled = value; |
| } |
| |
| void AppInventoryManager::SetFakesForTesting(FakesForTesting* fakes) { |
| DCHECK(fakes); |
| |
| WinHttpUrlFetcher::SetCreatorForTesting( |
| fakes->fake_win_http_url_fetcher_creator); |
| if (fakes->os_user_manager_for_testing) { |
| OSUserManager::SetInstanceForTesting(fakes->os_user_manager_for_testing); |
| } |
| } |
| |
| } // namespace credential_provider |