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

#include "components/policy/core/common/policy_proto_decoders.h"

#include <cstring>
#include <limits>
#include <memory>

#include "base/compiler_specific.h"
#include "base/json/json_reader.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
#include "components/policy/core/common/policy_logger.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/cloud_policy.pb.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/strings/grit/components_strings.h"

namespace policy {

namespace em = enterprise_management;

namespace {

const char kValue[] = "Value";
const char kLevel[] = "Level";
const char kRecommended[] = "Recommended";

// Returns true and sets |level| to a PolicyLevel if the policy has been set
// at that level. Returns false if the policy is not set, or has been set at
// the level of PolicyOptions::UNSET.
template <class AnyPolicyProto>
bool GetPolicyLevel(const AnyPolicyProto& policy_proto, PolicyLevel* level) {
  if (!policy_proto.has_value()) {
    return false;
  }
  if (!policy_proto.has_policy_options()) {
    *level = POLICY_LEVEL_MANDATORY;  // Default level.
    return true;
  }
  switch (policy_proto.policy_options().mode()) {
    case em::PolicyOptions::MANDATORY:
      *level = POLICY_LEVEL_MANDATORY;
      return true;
    case em::PolicyOptions::RECOMMENDED:
      *level = POLICY_LEVEL_RECOMMENDED;
      return true;
    case em::PolicyOptions::UNSET:
      return false;
  }
}

// Convert a BooleanPolicyProto to a bool base::Value.
base::Value DecodeBooleanProto(const em::BooleanPolicyProto& proto) {
  return base::Value(proto.value());
}

// Convert an IntegerPolicyProto to an int base::Value.
base::Value DecodeIntegerProto(const em::IntegerPolicyProto& proto,
                               std::string* error) {
  int64_t value = proto.value();

  if (value < std::numeric_limits<int>::min() ||
      value > std::numeric_limits<int>::max()) {
    LOG_POLICY(WARNING, POLICY_PROCESSING)
        << "Integer value " << value << " out of numeric limits";
    *error = "Number out of range - invalid int32";
    return base::Value(base::NumberToString(value));
  }

  return base::Value(static_cast<int>(value));
}

// Convert a StringPolicyProto to a string base::Value.
base::Value DecodeStringProto(const em::StringPolicyProto& proto) {
  return base::Value(proto.value());
}

// Convert a StringListPolicyProto to a List base::Value, where each list value
// is of Type::STRING.
base::Value DecodeStringListProto(const em::StringListPolicyProto& proto) {
  base::ListValue list_value;
  for (const auto& entry : proto.value().entries()) {
    list_value.Append(entry);
  }
  return base::Value(std::move(list_value));
}

// Convert a StringPolicyProto to a base::Value of any type (for example,
// Type::DICT or Type::LIST) by parsing it as JSON.
base::Value DecodeJsonProto(const em::StringPolicyProto& proto,
                            std::string* error) {
  const std::string& json = proto.value();
  auto value_with_error = base::JSONReader::ReadAndReturnValueWithError(
      json, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);

  if (!value_with_error.has_value()) {
    // Can't parse as JSON so return it as a string, and leave it to the handler
    // to validate.
    LOG_POLICY(WARNING, POLICY_PROCESSING) << "Invalid JSON: " << json;
    *error = value_with_error.error().message;
    return base::Value(json);
  }

  // Accept any Value type that parsed as JSON, and leave it to the handler to
  // convert and check the concrete type.
  error->clear();
  return std::move(*value_with_error);
}

bool PerProfileMatches(bool policy_per_profile,
                       PolicyPerProfileFilter per_profile_enum) {
  switch (per_profile_enum) {
    case PolicyPerProfileFilter::kTrue:
      return policy_per_profile;
    case PolicyPerProfileFilter::kFalse:
      return !policy_per_profile;
    case PolicyPerProfileFilter::kAny:
      return true;
  }
}

bool UseExternalDataFetcher(const char* policy_name,
                            StringPolicyType policy_type) {
  if (policy_type == StringPolicyType::EXTERNAL) {
    return true;
  }

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  if (UNSAFE_TODO(strcmp(policy_name, key::kWebAppInstallForceList)) == 0) {
    return true;
  }
#endif
  return false;
}

}  // namespace

ExtensionInstallDecision ConvertToExtensionInstallDecision(
    const em::ExtensionInstallPolicies& policies,
    const ExtensionIdAndVersion& extension_id_and_version) {
  for (em::ExtensionInstallPolicy policy : policies.policies()) {
    if (!policy.has_extension_id()) {
      VLOG_POLICY(1, POLICY_PROCESSING)
          << "ExtensionInstallCloudPolicy missing extension id";
      continue;
    }
    if (!policy.has_extension_version()) {
      VLOG_POLICY(1, POLICY_PROCESSING) << base::StringPrintf(
          "ExtensionInstallCloudPolicy - %s: missing version",
          policy.extension_id());
      continue;
    }
    std::string policy_key = base::StringPrintf("%s@%s", policy.extension_id(),
                                                policy.extension_version());

    if (!policy.has_action()) {
      VLOG_POLICY(1, POLICY_PROCESSING) << base::StringPrintf(
          "ExtensionInstallCloudPolicy - %s:missing action", policy_key);
      continue;
    }

    if (policy.extension_id() == extension_id_and_version.extension_id &&
        policy.extension_version() ==
            extension_id_and_version.extension_version) {
      std::set<em::ExtensionInstallPolicy::Reason> reasons;
      for (const auto& reason : policy.reasons()) {
        reasons.insert(static_cast<em::ExtensionInstallPolicy::Reason>(reason));
      }
      return ExtensionInstallDecision(policy.action(), std::move(reasons));
    }
  }
  VLOG_POLICY(1, POLICY_PROCESSING) << base::StringPrintf(
      "ExtensionInstallCloudPolicy - %s@%s:missing policy",
      extension_id_and_version.extension_id,
      extension_id_and_version.extension_version);
  return ExtensionInstallDecision();
}

void DecodeProtoFields(
    const em::ExtensionInstallPolicies& policies,
    base::WeakPtr<CloudExternalDataManager> external_data_manager,
    PolicySource source,
    PolicyScope scope,
    PolicyMap* map,
    PolicyPerProfileFilter per_profile) {
  std::map<std::string, base::Value> extension_id_to_policy_value;
  for (const em::ExtensionInstallPolicy& policy : policies.policies()) {
    if (!policy.has_extension_id()) {
      VLOG_POLICY(2, POLICY_PROCESSING)
          << "ExtensionInstallPolicy missing extension id";
      continue;
    }
    if (!policy.has_extension_version()) {
      VLOG_POLICY(2, POLICY_PROCESSING)
          << base::StringPrintf("ExtensionInstallPolicy - %s: missing version",
                                policy.extension_id());
      continue;
    }

    if (!policy.has_action()) {
      VLOG_POLICY(2, POLICY_PROCESSING) << base::StringPrintf(
          "ExtensionInstallPolicy - %s - version %s: missing action",
          policy.extension_id(), policy.extension_version());
      continue;
    }
    base::Value action(policy.action());
    base::ListValue reasons;
    for (const auto& reason : policy.reasons()) {
      reasons.Append(reason);
    }

    base::DictValue risk_levels;
    for (const auto& risk_level : policy.risk_levels()) {
      risk_levels.Set(risk_level.provider(),
                      base::Value(risk_level.risk_level()));
    }

    VLOG_POLICY(2, POLICY_PROCESSING) << base::StringPrintf(
        "ExtensionInstallPolicy - %s - version:%s action: %s, reasons: %s",
        policy.extension_id(), policy.extension_version(), action.DebugString(),
        reasons.DebugString());

    base::Value policy_value(base::Value::Type::DICT);
    policy_value.GetDict().Set("action", std::move(action));
    policy_value.GetDict().Set("reasons", std::move(reasons));
    policy_value.GetDict().Set("evaluated_risk_levels", std::move(risk_levels));

    if (!extension_id_to_policy_value.contains(policy.extension_id())) {
      extension_id_to_policy_value.emplace(
          policy.extension_id(), base::Value(base::Value::Type::DICT));
    }
    extension_id_to_policy_value[policy.extension_id()].GetDict().Set(
        policy.extension_version(), std::move(policy_value));
  }
  for (auto& [extension_id, policy_value] : extension_id_to_policy_value) {
    map->Set(extension_id, POLICY_LEVEL_MANDATORY, scope, source,
             std::move(policy_value), /*external_data_fetcher=*/nullptr);
  }
}

void DecodeProtoFields(
    const em::CloudPolicySettings& policy,
    base::WeakPtr<CloudExternalDataManager> external_data_manager,
    PolicySource source,
    PolicyScope scope,
    PolicyMap* map,
    PolicyPerProfileFilter per_profile) {
  PolicyLevel level;

  for (const BooleanPolicyAccess& access : kBooleanPolicyAccess) {
    if (!PerProfileMatches(access.per_profile, per_profile) ||
        !access.has_proto(policy)) {
      continue;
    }

    const em::BooleanPolicyProto& proto = access.get_proto(policy);
    if (!GetPolicyLevel(proto, &level)) {
      continue;
    }

    map->Set(access.policy_key, level, scope, source, DecodeBooleanProto(proto),
             nullptr);
  }

  for (const IntegerPolicyAccess& access : kIntegerPolicyAccess) {
    if (!PerProfileMatches(access.per_profile, per_profile) ||
        !access.has_proto(policy)) {
      continue;
    }

    const em::IntegerPolicyProto& proto = access.get_proto(policy);
    if (!GetPolicyLevel(proto, &level)) {
      continue;
    }

    std::string error;
    map->Set(access.policy_key, level, scope, source,
             DecodeIntegerProto(proto, &error), nullptr);
    if (!error.empty()) {
      map->AddMessage(access.policy_key, PolicyMap::MessageType::kError,
                      IDS_POLICY_PROTO_PARSING_ERROR,
                      {base::UTF8ToUTF16(error)});
    }
  }

  for (const StringPolicyAccess& access : kStringPolicyAccess) {
    if (!PerProfileMatches(access.per_profile, per_profile) ||
        !access.has_proto(policy)) {
      continue;
    }

    const em::StringPolicyProto& proto = access.get_proto(policy);
    if (!GetPolicyLevel(proto, &level)) {
      continue;
    }

    std::string error;
    base::Value value = (access.type == StringPolicyType::STRING)
                            ? DecodeStringProto(proto)
                            : DecodeJsonProto(proto, &error);

    // EXTERNAL policies represent a single piece of external data that is
    // retrieved by an ExternalDataFetcher.
    // kWebAppInstallForceList is currently the only policy that is a JSON
    // policy (containing mostly non-external data) which can contain
    // references to multiple pieces of external data as well. For that it
    // needs an ExternalDataFetcher. If we ever create a second such policy,
    // create a new type for it instead of special-casing the policies here.
    std::unique_ptr<ExternalDataFetcher> external_data_fetcher =
        UseExternalDataFetcher(access.policy_key, access.type)
            ? std::make_unique<ExternalDataFetcher>(external_data_manager,
                                                    access.policy_key)
            : nullptr;

    map->Set(access.policy_key, level, scope, source, std::move(value),
             std::move(external_data_fetcher));
    if (!error.empty()) {
      map->AddMessage(access.policy_key, PolicyMap::MessageType::kError,
                      IDS_POLICY_PROTO_PARSING_ERROR,
                      {base::UTF8ToUTF16(error)});
    }
  }

  for (const StringListPolicyAccess& access : kStringListPolicyAccess) {
    if (!PerProfileMatches(access.per_profile, per_profile) ||
        !access.has_proto(policy)) {
      continue;
    }

    const em::StringListPolicyProto& proto = access.get_proto(policy);
    if (!GetPolicyLevel(proto, &level)) {
      continue;
    }

    map->Set(access.policy_key, level, scope, source,
             DecodeStringListProto(proto), nullptr);
  }
}

bool ParseComponentPolicy(base::DictValue json_dict,
                          PolicyScope scope,
                          PolicySource source,
                          PolicyMap* policy,
                          std::string* error) {
  // Each top-level key maps a policy name to its description.
  //
  // Each description is an object that contains the policy value under the
  // "Value" key. The optional "Level" key is either "Mandatory" (default) or
  // "Recommended".
  for (auto [policy_name, description] : json_dict) {
    if (!description.is_dict()) {
      *error = "The JSON blob dictionary value is not a dictionary.";
      return false;
    }

    base::DictValue& description_dict = description.GetDict();
    std::optional<base::Value> value = description_dict.Extract(kValue);
    if (!value.has_value()) {
      *error = base::StrCat(
          {"The JSON blob dictionary value doesn't contain the required ",
           kValue, " field."});
      return false;
    }

    PolicyLevel level = POLICY_LEVEL_MANDATORY;
    const std::string* level_string = description_dict.FindString(kLevel);
    if (level_string && *level_string == kRecommended) {
      level = POLICY_LEVEL_RECOMMENDED;
    }

    policy->Set(policy_name, level, scope, source, std::move(value.value()),
                nullptr);
  }

  return true;
}

}  // namespace policy
