chromium/chrome/credential_provider/gaiacp/experiments_manager.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/credential_provider/gaiacp/experiments_manager.h"

#include <string_view>
#include <vector>

#include "base/files/file.h"
#include "base/json/json_reader.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/credential_provider/gaiacp/gcp_utils.h"
#include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"

namespace credential_provider {
namespace {

// Registry key to control whether experiments feature is enabled.
const wchar_t kExperimentsEnabledRegKey[] = L"experiments_enabled";

// Name of the keys found in the experiments server response.
const char kResponseExperimentsKeyName[] = "experiments";
const char kResponseFeatureKeyName[] = "feature";
const char kResponseValueKeyName[] = "value";

}  // namespace

// static
ExperimentsManager* ExperimentsManager::Get() {
  return *GetInstanceStorage();
}

// static
ExperimentsManager** ExperimentsManager::GetInstanceStorage() {
  static ExperimentsManager instance;
  static ExperimentsManager* instance_storage = &instance;
  return &instance_storage;
}

ExperimentsManager::ExperimentsManager() {
  if (ExperimentsEnabled()) {
    RegisterExperiments();
    ReloadAllExperiments();
  }
}

ExperimentsManager::~ExperimentsManager() = default;

void ExperimentsManager::RegisterExperiments() {
  experiments_to_values_.clear();
  for (ExperimentMetadata em : experiments) {
    // Experiment name to default value mapping is created prior to reading
    // experiments file.
    experiments_to_values_[em.name].first = em.default_value;
  }
}

// Reloads the experiment values from fetched experiments file for the given
// |sid|. Loads the fetched experiments values into |experiments_to_values_|.
bool ExperimentsManager::ReloadExperiments(const std::wstring& sid) {
  uint32_t open_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
  std::unique_ptr<base::File> experiments_file = GetOpenedFileForUser(
      sid, open_flags, kGcpwExperimentsDirectory, kGcpwUserExperimentsFileName);
  if (!experiments_file) {
    return false;
  }

  std::vector<char> buffer(experiments_file->GetLength());
  experiments_file->Read(0, buffer.data(), buffer.size());
  experiments_file.reset();

  std::optional<base::Value> experiments_data =
      base::JSONReader::Read(std::string_view(buffer.data(), buffer.size()),
                             base::JSON_ALLOW_TRAILING_COMMAS);
  if (!experiments_data || !experiments_data->is_dict()) {
    LOGFN(ERROR) << "Failed to read experiments data from file!";
    return false;
  }

  const base::Value::List* experiments_value =
      experiments_data->GetDict().FindList(kResponseExperimentsKeyName);
  if (!experiments_value) {
    LOGFN(ERROR) << "User experiments not found!";
    return false;
  }

  for (const auto& item : *experiments_value) {
    const auto& item_dict = item.GetDict();
    auto* f = item_dict.FindString(kResponseFeatureKeyName);
    auto* v = item_dict.FindString(kResponseValueKeyName);
    if (!f || !v) {
      LOGFN(WARNING) << "Either feature or value are not found!";
    }

    experiments_to_values_[*f].second[base::WideToUTF8(sid)] = *v;
  }
  return true;
}

// TODO(crbug.com/40155245): Reload experiments if they were fetched by ESA.
void ExperimentsManager::ReloadAllExperiments() {
  std::map<std::wstring, UserTokenHandleInfo> sid_to_gaia_id;
  HRESULT hr = GetUserTokenHandles(&sid_to_gaia_id);

  if (FAILED(hr)) {
    LOGFN(ERROR) << "Unable to get the list of associated users";
    return;
  }

  for (auto& sid : sid_to_gaia_id) {
    if (!ReloadExperiments(sid.first)) {
      LOGFN(ERROR) << "Error when loading experiments for user with sid "
                   << sid.first;
    }
  }
}

std::string ExperimentsManager::GetExperimentForUser(const std::string& sid,
                                                     Experiment experiment) {
  std::string experiment_name = experiments[experiment].name;

  // There shouldn't be a case where a registered experiment can't be found, but
  // just in case we return an empty string.
  if (experiments_to_values_.find(experiment_name) ==
      experiments_to_values_.end())
    return "";

  auto& sid_to_experiment_values =
      experiments_to_values_[experiment_name].second;

  // If the experiment value doesn't exist for the given sid, return the default
  // value.
  if (sid_to_experiment_values.find(sid) == sid_to_experiment_values.end()) {
    return experiments_to_values_[experiment_name].first;
  }

  return sid_to_experiment_values[sid];
}

bool ExperimentsManager::GetExperimentForUserAsBool(const std::string& sid,
                                                    Experiment experiment) {
  return base::ToLowerASCII(GetExperimentForUser(sid, experiment)) == "true";
}

bool ExperimentsManager::ExperimentsEnabled() const {
  return GetGlobalFlagOrDefault(kExperimentsEnabledRegKey, 1);
}

std::vector<std::string> ExperimentsManager::GetExperimentsList() const {
  std::vector<std::string> experiment_names;
  for (auto& experiment : experiments)
    experiment_names.push_back(experiment.name);
  return experiment_names;
}

}  // namespace credential_provider