chromium/chrome/browser/google/google_update_policy_fetcher_win.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/google/google_update_policy_fetcher_win.h"

#include <ATLComTime.h>
#include <wrl/client.h>

#include <tuple>
#include <utility>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util_win.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/com_init_util.h"
#include "base/win/scoped_bstr.h"
#include "chrome/browser/component_updater/updater_state.h"
#include "chrome/browser/google/google_update_policy_fetcher_win_util.h"
#include "chrome/install_static/install_util.h"
#include "chrome/updater/app/server/win/updater_legacy_idl.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/core/common/schema.h"
#include "components/strings/grit/components_strings.h"

namespace {

// TODO(crbug.com/40271852): Add unit tests for these GoogleUpdate policies.
constexpr char kAutoUpdateCheckPeriodMinutes[] = "AutoUpdateCheckPeriodMinutes";
constexpr char kDownloadPreference[] = "DownloadPreference";
constexpr char kForceInstallApps[] = "ForceInstallApps";
constexpr char kInstallPolicy[] = "InstallPolicy";
constexpr char kProxyMode[] = "ProxyMode";
constexpr char kProxyPacUrl[] = "ProxyPacUrl";
constexpr char kProxyServer[] = "ProxyServer";
constexpr char kRollbackToTargetVersion[] = "RollbackToTargetVersion";
constexpr char kTargetVersionPrefix[] = "TargetVersionPrefix";
constexpr char kTargetChannel[] = "TargetChannel";
constexpr char kUpdatePolicy[] = "UpdatePolicy";
constexpr char kUpdatesSuppressedDurationMin[] = "UpdatesSuppressedDurationMin";
constexpr char kUpdatesSuppressedStartHour[] = "UpdatesSuppressedStartHour";
constexpr char kUpdatesSuppressedStartMinute[] = "UpdatesSuppressedStartMinute";
constexpr char kCloudPolicyOverridesPlatformPolicy[] =
    "CloudPolicyOverridesPlatformPolicy";

// Adds the policy |policy_name| extracted from |policy| into |policies|.
// |value_override_function| is an optional function that modifies and overrides
// the value of the policy that needs to be store in |policies|. This function
// may be used to convert the extracted value, that is always a string into
// other types or formats.
void AddPolicy(const char* policy_name,
               IPolicyStatusValue* policy,
               policy::PolicyMap& policies,
               const PolicyValueOverrideFunction& value_override_function =
                   PolicyValueOverrideFunction()) {
  auto policy_entry =
      ConvertPolicyStatusValueToPolicyEntry(policy, value_override_function);
  if (policy_entry)
    policies.Set(policy_name, std::move(*policy_entry));
}

base::Time DateToTime(DATE date) {
  ::COleDateTime date_time(date);
  base::Time time;
  if (date_time.m_status == ::COleDateTime::valid) {
    std::ignore = base::Time::FromLocalExploded(
        {date_time.GetYear(), date_time.GetMonth(), date_time.GetDayOfWeek(),
         date_time.GetDay(), date_time.GetHour(), date_time.GetMinute(),
         date_time.GetSecond(), 0},
        &time);
  }
  return time;
}

// Returns the policies from GoogleUpdate. Requires GoogleUpdate
// version 1.3.36.91 (released 07-02-2021) or newer.
std::unique_ptr<policy::PolicyMap> GetGoogleUpdatePolicies(
    IPolicyStatus3* policy_status) {
  DCHECK(policy_status);

  policy_status->refreshPolicies();
  auto policies = std::make_unique<policy::PolicyMap>();
  base::win::ScopedBstr app_id(install_static::GetAppGuid());

  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_lastCheckPeriodMinutes(&policy)))
      AddPolicy(kAutoUpdateCheckPeriodMinutes, policy.Get(), *policies);
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_downloadPreferenceGroupPolicy(&policy)))
      AddPolicy(kDownloadPreference, policy.Get(), *policies);
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatus4> policy_status4;
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->QueryInterface(
            install_static::IsSystemInstall() ? __uuidof(IPolicyStatus4System)
                                              : __uuidof(IPolicyStatus4User),
            IID_PPV_ARGS_Helper(&policy_status4))) &&
        SUCCEEDED(
            policy_status4->get_cloudPolicyOverridesPlatformPolicy(&policy))) {
      AddPolicy(kCloudPolicyOverridesPlatformPolicy, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_forceInstallApps(
            install_static::IsSystemInstall(), &policy))) {
      AddPolicy(kForceInstallApps, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_effectivePolicyForAppInstalls(app_id.Get(),
                                                                   &policy))) {
      AddPolicy(kInstallPolicy, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_effectivePolicyForAppUpdates(app_id.Get(),
                                                                  &policy))) {
      AddPolicy(kUpdatePolicy, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    VARIANT_BOOL are_updates_suppressed = VARIANT_FALSE;
    if (SUCCEEDED(policy_status->get_updatesSuppressedTimes(
            &policy, &are_updates_suppressed))) {
      // A function that extracts the |index|-th value from a comma-separated
      // |initial_value|.
      const auto extract_value = [](int index, BSTR initial_value) {
        auto split = base::SplitString(
            initial_value, L",", base::WhitespaceHandling::TRIM_WHITESPACE,
            base::SplitResult::SPLIT_WANT_NONEMPTY);
        return base::Value(
            base::WideToUTF8(split.size() == 3 ? split[index] : L""));
      };
      AddPolicy(kUpdatesSuppressedStartHour, policy.Get(), *policies,
                base::BindRepeating(extract_value, 0));
      AddPolicy(kUpdatesSuppressedStartMinute, policy.Get(), *policies,
                base::BindRepeating(extract_value, 1));
      AddPolicy(kUpdatesSuppressedDurationMin, policy.Get(), *policies,
                base::BindRepeating(extract_value, 2));
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_isRollbackToTargetVersionAllowed(
            app_id.Get(), &policy))) {
      AddPolicy(kRollbackToTargetVersion, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(
            policy_status->get_targetVersionPrefix(app_id.Get(), &policy))) {
      AddPolicy(kTargetVersionPrefix, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_targetChannel(app_id.Get(), &policy))) {
      AddPolicy(kTargetChannel, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_proxyMode(&policy))) {
      AddPolicy(kProxyMode, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_proxyPacUrl(&policy))) {
      AddPolicy(kProxyPacUrl, policy.Get(), *policies);
    }
  }
  {
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy;
    if (SUCCEEDED(policy_status->get_proxyServer(&policy))) {
      AddPolicy(kProxyServer, policy.Get(), *policies);
    }
  }

  return policies;
}

// Returns the state from GoogleUpdate. Requires GoogleUpdate version 1.3.36.91
// (released 07-02-2021) or newer.
std::unique_ptr<GoogleUpdateState> GetGoogleUpdateState(
    IPolicyStatus3* policy_status) {
  DCHECK(policy_status);
  auto state = std::make_unique<GoogleUpdateState>();
  base::win::ScopedBstr updater_version;
  HRESULT last_com_res =
      policy_status->get_updaterVersion(updater_version.Receive());
  if (SUCCEEDED(last_com_res)) {
    DCHECK(updater_version.Length());
    state->version.assign(updater_version.Get(), updater_version.Length());
  }

  DATE last_checked_time;
  last_com_res = policy_status->get_lastCheckedTime(&last_checked_time);
  if (SUCCEEDED(last_com_res))
    state->last_checked_time = DateToTime(last_checked_time);

  return state;
}

}  // namespace

GoogleUpdatePoliciesAndState::GoogleUpdatePoliciesAndState() = default;

GoogleUpdatePoliciesAndState::~GoogleUpdatePoliciesAndState() = default;

base::Value GetGoogleUpdatePolicyNames() {
  base::Value::List names;
  for (const auto& key_value : GetGoogleUpdatePolicySchemas())
    names.Append(base::Value(key_value.first));
  return base::Value(std::move(names));
}

policy::PolicyConversions::PolicyToSchemaMap GetGoogleUpdatePolicySchemas() {
  // TODO(crbug.com/40722467): Use actual schemas.
  return policy::PolicyConversions::PolicyToSchemaMap{{
      {kAutoUpdateCheckPeriodMinutes, policy::Schema()},
      {kDownloadPreference, policy::Schema()},
      {kForceInstallApps, policy::Schema()},
      {kInstallPolicy, policy::Schema()},
      {kProxyMode, policy::Schema()},
      {kProxyPacUrl, policy::Schema()},
      {kProxyServer, policy::Schema()},
      {kRollbackToTargetVersion, policy::Schema()},
      {kTargetVersionPrefix, policy::Schema()},
      {kTargetChannel, policy::Schema()},
      {kUpdatePolicy, policy::Schema()},
      {kUpdatesSuppressedDurationMin, policy::Schema()},
      {kUpdatesSuppressedStartHour, policy::Schema()},
      {kUpdatesSuppressedStartMinute, policy::Schema()},
      {kCloudPolicyOverridesPlatformPolicy, policy::Schema()},
  }};
}

std::unique_ptr<GoogleUpdatePoliciesAndState>
GetGoogleUpdatePoliciesAndState() {
  base::win::AssertComInitialized();
  Microsoft::WRL::ComPtr<IPolicyStatus3> policy_status3;
  auto policies_and_state = std::make_unique<GoogleUpdatePoliciesAndState>();
  const bool is_system_install = install_static::IsSystemInstall();
  Microsoft::WRL::ComPtr<IUnknown> unknown;
  if (FAILED(::CoCreateInstance(is_system_install
                                    ? CLSID_PolicyStatusSystemClass
                                    : CLSID_PolicyStatusUserClass,
                                nullptr, CLSCTX_ALL, IID_PPV_ARGS(&unknown)))) {
    return policies_and_state;
  }

  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID.
  // Without this change, marshaling can load the typelib from the wrong hive
  // (HKCU instead of HKLM, or vice-versa).
  HRESULT hr = unknown.CopyTo(is_system_install ? __uuidof(IPolicyStatus3System)
                                                : __uuidof(IPolicyStatus3User),
                              IID_PPV_ARGS_Helper(&policy_status3));
  if (FAILED(hr)) {
    hr = unknown.As(&policy_status3);
    if (FAILED(hr)) {
      return policies_and_state;
    }
  }

  policies_and_state->policies = GetGoogleUpdatePolicies(policy_status3.Get());
  policies_and_state->state = GetGoogleUpdateState(policy_status3.Get());

  return policies_and_state;
}