910e62b5创建于 1月15日历史提交
// 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/device_api/managed_configuration_api.h"

#include <memory>
#include <optional>
#include <string>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_string_value_serializer.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/device_api/managed_configuration_store.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_thread.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/origin.h"

namespace {

constexpr char kManagedConfigurationDirectoryName[] = "Managed Configuration";
// Maximum configuration size is 5MB.
constexpr int kMaxConfigurationFileSize = 5 * 1024 * 1024;
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
    net::DefineNetworkTrafficAnnotation("managed_configuration_loader",
                                        R"(
        semantics {
          sender: "Web App Managed Configuration Downloader"
          description:
            "Fetches JSON managed configuration from the DM server, for the "
            "origins configured in the ManagedConfigurationPerOrigin policy. "
            "This enables the navigatior.device.getManagedConfiguration(keys) "
            "JavaScript function, to obtain the values of this configuration "
            "that correspond to provided keys. This configuration can only be "
            "set for force-installed web applications, which are defined by "
            "WebAppInstallForceList policy."
          trigger:
            "Changes in the ManagedConfigurationPerOrigin policy."
          data: "No user data."
          destination: GOOGLE_OWNED_SERVICE
        }
        policy {
          cookies_allowed: NO
          setting:
            "This feature cannot be disabled in settings, but it will only be "
            "sent if the device administrator sets up a configuration policy "
            "for a particular origin.",
          chrome_policy {
            ManagedConfigurationPerOrigin {
              ManagedConfigurationPerOrigin: "[]"
            }
          }
        })");

// Converts url::Origin into the key that can be used for filenames/dictionary
// keys.
std::string GetOriginEncoded(const url::Origin& origin) {
  return base::HexEncode(origin.Serialize());
}

}  // namespace

const char ManagedConfigurationAPI::kOriginKey[] = "origin";
const char ManagedConfigurationAPI::kManagedConfigurationUrlKey[] =
    "managed_configuration_url";
const char ManagedConfigurationAPI::kManagedConfigurationHashKey[] =
    "managed_configuration_hash";

class ManagedConfigurationAPI::ManagedConfigurationDownloader {
 public:
  explicit ManagedConfigurationDownloader(const std::string& data_hash)
      : data_hash_(data_hash) {}
  ~ManagedConfigurationDownloader() = default;
  ManagedConfigurationDownloader(const ManagedConfigurationDownloader&) =
      delete;
  ManagedConfigurationDownloader& operator=(
      const ManagedConfigurationDownloader&) = delete;

  void Fetch(const std::string& data_url,
             base::OnceCallback<void(std::optional<std::string>)> callback) {
    // URLLoaders should be created at UI thread.
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    auto resource_request = std::make_unique<network::ResourceRequest>();
    resource_request->url = GURL(data_url);
    resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;

    simple_loader_ = network::SimpleURLLoader::Create(
        std::move(resource_request), kTrafficAnnotation);

    simple_loader_->SetRetryOptions(
        /* max_retries=*/3,
        network::SimpleURLLoader::RETRY_ON_5XX |
            network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);

    SystemNetworkContextManager* system_network_context_manager =
        g_browser_process->system_network_context_manager();
    network::mojom::URLLoaderFactory* loader_factory =
        system_network_context_manager->GetURLLoaderFactory();

    simple_loader_->DownloadToString(loader_factory, std::move(callback),
                                     kMaxConfigurationFileSize);
  }

  std::string hash() const { return data_hash_; }

 private:
  std::unique_ptr<network::SimpleURLLoader> simple_loader_;
  const std::string data_hash_;
};

ManagedConfigurationAPI::ManagedConfigurationAPI(Profile* profile)
    : profile_(profile),
      stores_path_(
          profile->GetPath().AppendASCII(kManagedConfigurationDirectoryName)) {
  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(profile_->GetPrefs());
  pref_change_registrar_->Add(
      prefs::kManagedConfigurationPerOrigin,
      base::BindRepeating(
          &ManagedConfigurationAPI::OnConfigurationPolicyChanged,
          weak_ptr_factory_.GetWeakPtr()));
  OnConfigurationPolicyChanged();
}

ManagedConfigurationAPI::~ManagedConfigurationAPI() = default;

// static
void ManagedConfigurationAPI::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterListPref(prefs::kManagedConfigurationPerOrigin);
  registry->RegisterDictionaryPref(
      prefs::kLastManagedConfigurationHashForOrigin);
}

void ManagedConfigurationAPI::GetOriginPolicyConfiguration(
    const url::Origin& origin,
    const std::vector<std::string>& keys,
    base::OnceCallback<void(std::optional<base::Value::Dict>)> callback) {
  if (!CanHaveManagedStore(origin)) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  if (!base::Contains(store_map_, origin)) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  store_map_[origin]
      .AsyncCall(&ManagedConfigurationStore::Get)
      .WithArgs(keys)
      .Then(std::move(callback));
}

void ManagedConfigurationAPI::AddObserver(Observer* observer) {
  url::Origin origin = observer->GetOrigin();
  if (CanHaveManagedStore(origin)) {
    MaybeCreateStoreForOrigin(origin);
    observers_[origin].AddObserver(observer);
  } else {
    unmanaged_observers_.insert(observer);
  }
}

void ManagedConfigurationAPI::RemoveObserver(Observer* observer) {
  auto iter = unmanaged_observers_.find(observer);
  if (iter != unmanaged_observers_.end()) {
    unmanaged_observers_.erase(iter);
    return;
  }

  observers_[observer->GetOrigin()].RemoveObserver(observer);
}

bool ManagedConfigurationAPI::CanHaveManagedStore(const url::Origin& origin) {
  return base::Contains(managed_origins_, origin);
}

const std::set<url::Origin>& ManagedConfigurationAPI::GetManagedOrigins()
    const {
  return managed_origins_;
}

void ManagedConfigurationAPI::OnConfigurationPolicyChanged() {
  const base::Value::List& managed_configurations =
      profile_->GetPrefs()->GetList(prefs::kManagedConfigurationPerOrigin);

  std::set<url::Origin> current_origins;

  for (const auto& entry : managed_configurations) {
    const auto& entry_dict = entry.GetDict();
    const std::string* url = entry_dict.FindString(kOriginKey);
    if (!url) {
      continue;
    }
    const url::Origin origin = url::Origin::Create(GURL(*url));
    if (origin.opaque()) {
      continue;
    }

    const std::string* configuration_url =
        entry_dict.FindString(kManagedConfigurationUrlKey);
    const std::string* configuration_hash =
        entry_dict.FindString(kManagedConfigurationHashKey);
    current_origins.insert(origin);

    if (!configuration_url || !configuration_hash) {
      continue;
    }
    UpdateStoredDataForOrigin(origin, *configuration_url, *configuration_hash);
  }

  // We need to clean configurations for origins that got their entry removed.
  for (const auto& store_entry : store_map_) {
    if (!base::Contains(current_origins, store_entry.first)) {
      UpdateStoredDataForOrigin(store_entry.first, std::string(),
                                std::string());
    }
  }

  managed_origins_.swap(current_origins);
  PromoteObservers();
}

void ManagedConfigurationAPI::MaybeCreateStoreForOrigin(
    const url::Origin& origin) {
  if (base::Contains(store_map_, origin)) {
    return;
  }

  // Create the store now, and serve the cached policy until the PolicyService
  // sends updated values.
  store_map_[origin] = base::SequenceBound<ManagedConfigurationStore>(
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}),
      origin, GetStoreLocation(origin));
}

base::FilePath ManagedConfigurationAPI::GetStoreLocation(
    const url::Origin& origin) {
  return stores_path_.AppendASCII(GetOriginEncoded(origin));
}

void ManagedConfigurationAPI::UpdateStoredDataForOrigin(
    const url::Origin& origin,
    const std::string& configuration_url,
    const std::string& configuration_hash) {
  const std::string* last_hash_value =
      profile_->GetPrefs()
          ->GetDict(prefs::kLastManagedConfigurationHashForOrigin)
          .FindString(GetOriginEncoded(origin));

  // Nothing to be stored here, the hash value is the same.
  if (last_hash_value && *last_hash_value == configuration_hash) {
    return;
  }

  if (configuration_url.empty()) {
    PostStoreConfiguration(origin, base::Value::Dict());
    return;
  }

  // Check whether there is already a downloader.
  if (downloaders_[origin]) {
    // If it downloads the same data already, do nothing.
    if (downloaders_[origin]->hash() == configuration_hash) {
      return;
    }
    // Cancel it otherwise.
    downloaders_[origin].reset();
  }

  downloaders_[origin] =
      std::make_unique<ManagedConfigurationDownloader>(configuration_hash);
  downloaders_[origin]->Fetch(
      configuration_url, base::BindOnce(&ManagedConfigurationAPI::DecodeData,
                                        weak_ptr_factory_.GetWeakPtr(), origin,
                                        configuration_hash));
}

void ManagedConfigurationAPI::DecodeData(const url::Origin& origin,
                                         const std::string& url_hash,
                                         std::optional<std::string> data) {
  downloaders_[origin].reset();
  if (!data) {
    return;
  }

  // First, we have to parse JSON file in an isolated sandbox so that we don't
  // have to worry about potentially risky values.
  data_decoder::DataDecoder::ParseJsonIsolated(
      *data,
      base::BindOnce(&ManagedConfigurationAPI::ProcessDecodedConfiguration,
                     weak_ptr_factory_.GetWeakPtr(), origin, url_hash));
}

void ManagedConfigurationAPI::ProcessDecodedConfiguration(
    const url::Origin& origin,
    const std::string& url_hash,
    const data_decoder::DataDecoder::ValueOrError decoding_result) {
  if (!decoding_result.has_value() || !decoding_result->is_dict()) {
    VLOG(1) << "Could not fetch managed configuration for app with origin = "
            << origin.Serialize();
    PostStoreConfiguration(origin, base::Value::Dict());
    return;
  }
  ScopedDictPrefUpdate update(profile_->GetPrefs(),
                              prefs::kLastManagedConfigurationHashForOrigin);
  update->Set(GetOriginEncoded(origin), url_hash);

  // We need to transform each value into a string.
  base::Value::Dict result_dict;
  for (auto item : decoding_result->GetDict()) {
    std::string result;
    JSONStringValueSerializer serializer(&result);
    serializer.Serialize(item.second);
    result_dict.SetByDottedPath(item.first, result);
  }

  PostStoreConfiguration(origin, std::move(result_dict));
}

void ManagedConfigurationAPI::PostStoreConfiguration(
    const url::Origin& origin,
    base::Value::Dict configuration) {
  MaybeCreateStoreForOrigin(origin);
  store_map_[origin]
      .AsyncCall(&ManagedConfigurationStore::SetCurrentPolicy)
      .WithArgs(std::move(configuration))
      .Then(base::BindOnce(
          &ManagedConfigurationAPI::InformObserversIfConfigurationChanged,
          weak_ptr_factory_.GetWeakPtr(), origin));
}

void ManagedConfigurationAPI::InformObserversIfConfigurationChanged(
    const url::Origin& origin,
    bool has_changed) {
  if (!has_changed || !base::Contains(observers_, origin)) {
    return;
  }

  for (auto& observer : observers_[origin]) {
    observer.OnManagedConfigurationChanged();
  }
}

void ManagedConfigurationAPI::PromoteObservers() {
  for (auto it = unmanaged_observers_.begin();
       it != unmanaged_observers_.end();) {
    if (CanHaveManagedStore((*it)->GetOrigin())) {
      auto* observer = (*it).get();
      it = unmanaged_observers_.erase(it);
      AddObserver(observer);
    } else {
      ++it;
    }
  }
}