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

#include "chromeos/components/onc/onc_utils.h"

#include <optional>
#include <string>
#include <vector>

#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/json/json_file_value_serializer.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "chromeos/components/onc/onc_signature.h"
#include "chromeos/components/onc/onc_test_utils.h"
#include "chromeos/components/onc/variable_expander.h"
#include "components/onc/onc_constants.h"
#include "onc_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::Pointee;
namespace chromeos::onc {

TEST(ONCDecrypterTest, BrokenEncryptionIterations) {
  base::DictValue encrypted_onc =
      test_utils::ReadTestDictionary("broken-encrypted-iterations.onc");

  std::optional<base::DictValue> decrypted_onc = Decrypt(encrypted_onc);

  EXPECT_FALSE(decrypted_onc.has_value());
}

TEST(ONCDecrypterTest, BrokenEncryptionZeroIterations) {
  base::DictValue encrypted_onc =
      test_utils::ReadTestDictionary("broken-encrypted-zero-iterations.onc");

  std::optional<base::DictValue> decrypted_onc = Decrypt(encrypted_onc);

  EXPECT_FALSE(decrypted_onc.has_value());
}

TEST(ONCDecrypterTest, LoadEncryptedOnc) {
  base::DictValue encrypted_onc =
      test_utils::ReadTestDictionary("encrypted.onc");
  base::DictValue expected_decrypted_onc =
      test_utils::ReadTestDictionary("decrypted.onc");

  std::string error;
  std::optional<base::DictValue> actual_decrypted_onc = Decrypt(encrypted_onc);

  ASSERT_TRUE(actual_decrypted_onc.has_value());
  EXPECT_TRUE(test_utils::Equals(&expected_decrypted_onc,
                                 &actual_decrypted_onc.value()));
}

namespace {

const char* kLoginId = "hans";
const char* kLoginEmail = "hans@my.domain.com";
const char* kDeviceSerialNumber = "ABC123DEF456";

const std::vector<std::string>& GetValidApnTypes() {
  static const base::NoDestructor<std::vector<std::string>> val({
      ::onc::cellular_apn::kIpTypeAutomatic,
      ::onc::cellular_apn::kIpTypeIpv4,
      ::onc::cellular_apn::kIpTypeIpv4Ipv6,
      ::onc::cellular_apn::kIpTypeIpv6,
  });
  return *val;
}

const std::vector<std::string>& GetTestAdminApnListAllIds() {
  static const base::NoDestructor<std::vector<std::string>> val(
      {"id-1", "id-2", "id-3"});
  return *val;
}

const std::vector<std::string>& GetTestAdminApnListSubsetIds() {
  static const base::NoDestructor<std::vector<std::string>> val(
      {"id-1", "id-3"});
  return *val;
}

const std::vector<std::string>& GetTestNonAdminApnListIds() {
  static const base::NoDestructor<std::vector<std::string>> val(
      {"id-x", "id-y"});
  return *val;
}

base::flat_map<std::string, std::string> GetTestStringSubstitutions() {
  base::flat_map<std::string, std::string> substitutions;
  substitutions[::onc::substitutes::kLoginID] = kLoginId;
  substitutions[::onc::substitutes::kLoginEmail] = kLoginEmail;
  substitutions[::onc::substitutes::kDeviceSerialNumber] = kDeviceSerialNumber;
  return substitutions;
}

}  // namespace

TEST(ONCStringExpansion, OpenVPN) {
  base::DictValue vpn_onc = test_utils::ReadTestDictionary("valid_openvpn.onc");

  VariableExpander variable_expander(GetTestStringSubstitutions());
  ExpandStringsInOncObject(kNetworkConfigurationSignature, variable_expander,
                           &vpn_onc);

  std::string* actual_expanded =
      vpn_onc.FindStringByDottedPath("VPN.OpenVPN.Username");
  ASSERT_TRUE(actual_expanded);
  EXPECT_EQ(*actual_expanded, std::string("abc ") + kLoginEmail + " def");
}

TEST(ONCStringExpansion, WiFiEap) {
  base::DictValue wifi_onc =
      test_utils::ReadTestDictionary("wifi_clientcert_with_cert_pems.onc");

  VariableExpander variable_expander(GetTestStringSubstitutions());
  ExpandStringsInOncObject(kNetworkConfigurationSignature, variable_expander,
                           &wifi_onc);

  std::string* actual_expanded =
      wifi_onc.FindStringByDottedPath("WiFi.EAP.Identity");
  ASSERT_TRUE(actual_expanded);
  EXPECT_EQ(*actual_expanded,
            std::string("abc ") + kLoginId + "@my.domain.com");
}

// Test that placeholders are being expanded in the ClientCertPattern fields.
TEST(ONCStringExpansion, WiFiEapPlaceholdersAreReplaced) {
  base::DictValue wifi_onc = test_utils::ReadTestDictionary(
      "wifi_clientcert_with_cert_placeholders.onc");

  VariableExpander variable_expander(GetTestStringSubstitutions());
  ExpandStringsInOncObject(kNetworkConfigurationSignature, variable_expander,
                           &wifi_onc);

  base::DictValue* expanded_issuer =
      wifi_onc.FindDictByDottedPath("WiFi.EAP.ClientCertPattern.Issuer");
  ASSERT_TRUE(expanded_issuer);
  EXPECT_EQ(*expanded_issuer,
            base::DictValue()
                .Set("CommonName", kDeviceSerialNumber)
                .Set("Locality", kDeviceSerialNumber)
                .Set("Organization", kDeviceSerialNumber)
                .Set("OrganizationalUnit", kDeviceSerialNumber));

  base::DictValue* expanded_subject =
      wifi_onc.FindDictByDottedPath("WiFi.EAP.ClientCertPattern.Subject");
  ASSERT_TRUE(expanded_subject);
  EXPECT_EQ(*expanded_subject,
            base::DictValue()
                .Set("CommonName", kDeviceSerialNumber)
                .Set("Locality", kDeviceSerialNumber)
                .Set("Organization", kDeviceSerialNumber)
                .Set("OrganizationalUnit", kDeviceSerialNumber));
}

// Test that strings that contain names of placeholders, but don't have the ${}
// brackets around them are treated like normal strings and are not replaced.
TEST(ONCStringExpansion, WiFiEapAlmostPlaceholdersAreNotReplaced) {
  base::DictValue wifi_onc = test_utils::ReadTestDictionary(
      "wifi_clientcert_with_almost_placeholders.onc");

  VariableExpander variable_expander(GetTestStringSubstitutions());
  ExpandStringsInOncObject(kNetworkConfigurationSignature, variable_expander,
                           &wifi_onc);

  std::string* name = wifi_onc.FindStringByDottedPath("Name");
  ASSERT_TRUE(name);
  EXPECT_EQ(*name, "DEVICE_SERIAL_NUMBER_placeholder_test");

  constexpr char kExpectedValue[] = "DEVICE_SERIAL_NUMBER";

  base::DictValue* expanded_issuer =
      wifi_onc.FindDictByDottedPath("WiFi.EAP.ClientCertPattern.Issuer");
  ASSERT_TRUE(expanded_issuer);
  EXPECT_EQ(*expanded_issuer, base::DictValue()
                                  .Set("CommonName", kExpectedValue)
                                  .Set("Locality", kExpectedValue)
                                  .Set("Organization", kExpectedValue)
                                  .Set("OrganizationalUnit", kExpectedValue));

  base::DictValue* expanded_subject =
      wifi_onc.FindDictByDottedPath("WiFi.EAP.ClientCertPattern.Subject");
  ASSERT_TRUE(expanded_subject);
  EXPECT_EQ(*expanded_subject, base::DictValue()
                                   .Set("CommonName", kExpectedValue)
                                   .Set("Locality", kExpectedValue)
                                   .Set("Organization", kExpectedValue)
                                   .Set("OrganizationalUnit", kExpectedValue));
}

TEST(ONCResolveServerCertRefs, ResolveServerCertRefs) {
  base::DictValue test_cases = test_utils::ReadTestDictionary(
      "network_configs_with_resolved_certs.json");

  CertPEMsByGUIDMap certs;
  certs["cert_google"] = "pem_google";
  certs["cert_webkit"] = "pem_webkit";

  for (auto iter : test_cases) {
    SCOPED_TRACE("Test case: " + iter.first);

    const base::DictValue* test_case_dict = iter.second.GetIfDict();
    ASSERT_TRUE(test_case_dict);

    const base::ListValue* networks_with_cert_refs =
        test_case_dict->FindList("WithCertRefs");
    ASSERT_TRUE(networks_with_cert_refs);
    const base::ListValue* expected_resolved_onc =
        test_case_dict->FindList("WithResolvedRefs");
    ASSERT_TRUE(expected_resolved_onc);

    bool expected_success =
        (networks_with_cert_refs->size() == expected_resolved_onc->size());

    base::ListValue actual_resolved_onc(networks_with_cert_refs->Clone());
    bool success = ResolveServerCertRefsInNetworks(certs, actual_resolved_onc);
    EXPECT_EQ(expected_success, success);
    EXPECT_EQ(*expected_resolved_onc, actual_resolved_onc);
  }
}

TEST(ONCUtils, SetHiddenSSIDField_WithNoValueSet) {
  // WiFi configuration that doesn't have HiddenSSID field set.
  base::DictValue wifi_onc =
      test_utils::ReadTestDictionary("wifi_clientcert_with_cert_pems.onc");
  base::DictValue* wifi_fields = wifi_onc.FindDict("WiFi");
  ASSERT_TRUE(wifi_fields);

  ASSERT_FALSE(wifi_fields->Find(::onc::wifi::kHiddenSSID));
  SetHiddenSSIDField(*wifi_fields);
  base::Value* hidden_ssid_field = wifi_fields->Find(::onc::wifi::kHiddenSSID);
  ASSERT_TRUE(hidden_ssid_field);
  EXPECT_FALSE(hidden_ssid_field->GetBool());
}

TEST(ONCUtils, SetHiddenSSIDField_WithValueSetFalse) {
  // WiFi configuration that have HiddenSSID field set to false.
  base::DictValue wifi_onc = test_utils::ReadTestDictionary(
      "translation_of_shill_wifi_with_state.onc");
  base::DictValue* wifi_fields = wifi_onc.FindDict("WiFi");
  ASSERT_TRUE(wifi_fields);

  ASSERT_TRUE(wifi_fields->Find(::onc::wifi::kHiddenSSID));
  SetHiddenSSIDField(*wifi_fields);
  EXPECT_FALSE(wifi_fields->Find(::onc::wifi::kHiddenSSID)->GetBool());
}

TEST(ONCUtils, SetHiddenSSIDField_WithValueSetTrue) {
  // WiFi configuration that have HiddenSSID field set to true.
  base::DictValue wifi_onc =
      test_utils::ReadTestDictionary("wifi_with_hidden_ssid.onc");
  base::DictValue* wifi_fields = wifi_onc.FindDict("WiFi");
  ASSERT_TRUE(wifi_fields);

  ASSERT_TRUE(wifi_fields->Find(::onc::wifi::kHiddenSSID));
  SetHiddenSSIDField(*wifi_fields);
  EXPECT_TRUE(wifi_fields->Find(::onc::wifi::kHiddenSSID)->GetBool());
}

TEST(ONCUtils, ParseAndValidateOncForImport_ApnProvided) {
  const auto onc_blob = test_utils::ReadTestData("valid_cellular_with_apn.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  base::DictValue expected;
  expected.Set(::onc::cellular_apn::kAccessPointName, "test-apn");
  expected.Set(::onc::cellular_apn::kAuthentication, "");
  expected.Set(::onc::cellular_apn::kUsername, "test-username");
  expected.Set(::onc::cellular_apn::kPassword, "test-password");

  const auto* cellular_apn =
      network_configs[0].GetDict().FindByDottedPath("Cellular.APN");
  EXPECT_THAT(cellular_apn,
              Pointee(base::test::DictionaryHasValues(std::move(expected))));
}

TEST(ONCUtils, ParseAndValidateOncForImport_NoApnProvided) {
  const auto onc_blob = test_utils::ReadTestData("valid_cellular_no_apn.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  const auto* cellular_apn =
      network_configs[0].GetDict().FindByDottedPath("Cellular.APN");
  ASSERT_NE(nullptr, cellular_apn);
  ASSERT_NE(nullptr, cellular_apn->GetDict().Find(::onc::kRecommended));

  base::ListValue recommended =
      base::ListValue()
          .Append(::onc::cellular_apn::kAccessPointName)
          .Append(::onc::cellular_apn::kAttach)
          .Append(::onc::cellular_apn::kAuthentication)
          .Append(::onc::cellular_apn::kUsername)
          .Append(::onc::cellular_apn::kPassword);

  base::DictValue expected;
  expected.Set(::onc::kRecommended, std::move(recommended));
  EXPECT_THAT(cellular_apn,
              Pointee(base::test::DictionaryHasValues(std::move(expected))));
}

TEST(ONCUtils, ParseAndValidateOncForImport_APNAccessPointName) {
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;
  test_utils::TestToplevelApnData apn_data;

  // Test APN with only Access Point Name
  apn_data.access_point_name = "test-access-point-name";
  std::string onc_blob =
      test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  // Failure if APN has empty Access Point Name
  apn_data.access_point_name = "";
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  //  Failure if APN has no AccessPointName field.
  apn_data.access_point_name = std::nullopt;
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));
}

TEST(ONCUtils, ParseAndValidateOncForImport_APNApnType) {
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;
  test_utils::TestToplevelApnData apn_data;

  // Test that no APN Types field is fine
  apn_data.apn_types = std::nullopt;
  std::string onc_blob =
      test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  // Test valid APN types
  apn_data.apn_types = {
      ::onc::cellular_apn::kApnTypeDefault,
      ::onc::cellular_apn::kApnTypeAttach,
      ::onc::cellular_apn::kApnTypeTether,
  };
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  // Test invalid APN types
  apn_data.apn_types = {"invalidApn", ::onc::cellular_apn::kApnTypeDefault};
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  // Test empty APN types array
  apn_data.apn_types->clear();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));
}

TEST(ONCUtils, ParseAndValidateOncForImport_APNIpType) {
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;
  test_utils::TestToplevelApnData apn_data;

  // Test that no IpType provided is fine
  std::string onc_blob =
      test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  // Test valid IpTypes
  for (const std::string& ip_type : GetValidApnTypes()) {
    apn_data.ip_type = ip_type;
    onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);

    ASSERT_TRUE(ParseAndValidateOncForImport(
        onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
        &global_network_config, &certificates));
  }

  // Failure if Invalid IP type
  apn_data.ip_type = "InvalidApnType";
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));
}

TEST(ONCUtils, ParseAndValidateOncForImport_AdminAPNsExistForAdminAPNIds) {
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;
  test_utils::TestToplevelApnData apn_data;
  apn_data.admin_apn_list_ids = GetTestAdminApnListAllIds();

  apn_data.psim_admin_assigned_apn_ids = GetTestAdminApnListSubsetIds();
  std::string onc_blob =
      test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  apn_data.psim_admin_assigned_apn_ids = GetTestAdminApnListSubsetIds();
  apn_data.admin_assigned_apn_ids = std::vector<std::string>();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  apn_data.psim_admin_assigned_apn_ids = std::vector<std::string>();
  apn_data.admin_assigned_apn_ids = GetTestAdminApnListSubsetIds();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  apn_data.psim_admin_assigned_apn_ids = GetTestAdminApnListSubsetIds();
  apn_data.admin_assigned_apn_ids = GetTestAdminApnListSubsetIds();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));
}

TEST(ONCUtils, ParseAndValidateOncForImport_AdminAPNsDoNotExistForAdminAPNIds) {
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;
  test_utils::TestToplevelApnData apn_data;
  apn_data.admin_apn_list_ids = GetTestAdminApnListAllIds();

  apn_data.psim_admin_assigned_apn_ids = GetTestNonAdminApnListIds();
  apn_data.admin_assigned_apn_ids = std::nullopt;
  std::string onc_blob =
      test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  apn_data.psim_admin_assigned_apn_ids = std::nullopt;
  apn_data.admin_assigned_apn_ids = GetTestNonAdminApnListIds();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  apn_data.psim_admin_assigned_apn_ids = GetTestNonAdminApnListIds();
  apn_data.admin_assigned_apn_ids = std::vector<std::string>();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  apn_data.psim_admin_assigned_apn_ids = std::vector<std::string>();
  apn_data.admin_assigned_apn_ids = GetTestNonAdminApnListIds();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  apn_data.psim_admin_assigned_apn_ids = GetTestAdminApnListAllIds();
  apn_data.admin_assigned_apn_ids = GetTestNonAdminApnListIds();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  apn_data.psim_admin_assigned_apn_ids = GetTestNonAdminApnListIds();
  apn_data.admin_assigned_apn_ids = GetTestAdminApnListAllIds();
  onc_blob = test_utils::GenerateTopLevelWithCellularWithAPNAsJSON(apn_data);
  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));
}

TEST(ONCUtils, ParseAndValidateOncForImport_CustomApnListRecommendedByDefault) {
  const auto onc_blob =
      test_utils::ReadTestData("valid_cellular_no_recommended.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  const auto* recommended =
      network_configs[0].GetDict().FindByDottedPath("Cellular.Recommended");
  ASSERT_NE(nullptr, recommended);

  base::ListValue expected_recommended =
      base::ListValue().Append(::onc::cellular::kCustomAPNList);

  EXPECT_EQ(expected_recommended, *recommended);
}

TEST(
    ONCUtils,
    ParseAndValidateOncForImport_CustomApnListRecommendedWhenApnModificationNotProvided) {
  const auto onc_blob = test_utils::ReadTestData(
      "managed_cellular_no_recommended_allow_apn_modification_not_provided."
      "onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  const auto* recommended =
      network_configs[0].GetDict().FindByDottedPath("Cellular.Recommended");
  ASSERT_NE(nullptr, recommended);

  base::ListValue expected_recommended =
      base::ListValue().Append(::onc::cellular::kCustomAPNList);

  EXPECT_EQ(expected_recommended, *recommended);
}

TEST(
    ONCUtils,
    ParseAndValidateOncForImport_CustomApnListRecommendedWhenApnModificationAllowed) {
  const auto onc_blob = test_utils::ReadTestData(
      "managed_cellular_no_recommended_allow_apn_modification_true.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  const auto* recommended =
      network_configs[0].GetDict().FindByDottedPath("Cellular.Recommended");
  ASSERT_NE(nullptr, recommended);

  base::ListValue expected_recommended =
      base::ListValue().Append(::onc::cellular::kCustomAPNList);

  EXPECT_EQ(expected_recommended, *recommended);
}

TEST(
    ONCUtils,
    ParseAndValidateOncForImport_CustomApnListNotRecommendeWhenApnModificationProhibited) {
  const auto onc_blob = test_utils::ReadTestData(
      "managed_cellular_no_recommended_allow_apn_modification_false.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  const auto* recommended =
      network_configs[0].GetDict().FindByDottedPath("Cellular.Recommended");
  EXPECT_EQ(nullptr, recommended);
}

TEST(ONCUtils, ParseAndValidateOncForImport_AdminApnProvided) {
  const auto onc_blob = test_utils::ReadTestData(
      "managed_toplevel_with_multiple_cellular_and_admin_apns.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  // Expected custom APN list for the first network configuration
  base::ListValue expected_custom_apns;

  // First expected custom APN details for the first network configuration
  base::DictValue first_custom_apn_details;
  first_custom_apn_details.Set(::onc::cellular_apn::kId, "admin-apn-id-y");
  first_custom_apn_details.Set(::onc::cellular_apn::kAccessPointName,
                               "test-apn-admin-y");
  first_custom_apn_details.Set(::onc::cellular_apn::kAuthentication, "");
  first_custom_apn_details.Set(::onc::cellular_apn::kUsername,
                               "test-username-y");
  first_custom_apn_details.Set(::onc::cellular_apn::kPassword,
                               "test-password-y");
  first_custom_apn_details.Set(::onc::cellular_apn::kSource,
                               ::onc::cellular_apn::kSourceAdmin);

  // Second expected custom APN details for the first network configuration
  base::DictValue second_custom_apn_details;
  second_custom_apn_details.Set(::onc::cellular_apn::kId, "admin-apn-id-x");
  second_custom_apn_details.Set(::onc::cellular_apn::kAccessPointName,
                                "test-apn-admin-x");
  second_custom_apn_details.Set(::onc::cellular_apn::kAuthentication, "");
  second_custom_apn_details.Set(::onc::cellular_apn::kUsername,
                                "test-username-x");
  second_custom_apn_details.Set(::onc::cellular_apn::kPassword,
                                "test-password-x");
  second_custom_apn_details.Set(::onc::cellular_apn::kSource,
                                ::onc::cellular_apn::kSourceAdmin);

  // Add the APN details to the expected custom APN list for the first network
  // configuration
  expected_custom_apns.Append(std::move(first_custom_apn_details));
  expected_custom_apns.Append(std::move(second_custom_apn_details));

  // Get the actual APN list from the first network configuration
  const auto* actual_custom_apns =
      network_configs[0].GetDict().FindByDottedPath("Cellular.CustomAPNList");

  // Verify that the actual APN list for the first network configuration matches
  // the expected custom APN list
  EXPECT_TRUE(actual_custom_apns->GetList() == expected_custom_apns);

  // Expected custom APN list for the second network configuration
  base::ListValue expected_custom_apns_2;

  // First expected custom APN details for the second network configuration
  base::DictValue first_custom_apn_details_2;
  first_custom_apn_details_2.Set(::onc::cellular_apn::kId, "admin-apn-id-z");
  first_custom_apn_details_2.Set(::onc::cellular_apn::kAccessPointName,
                                 "test-apn-admin-z");
  first_custom_apn_details_2.Set(::onc::cellular_apn::kAuthentication, "");
  first_custom_apn_details_2.Set(::onc::cellular_apn::kUsername,
                                 "test-username-z");
  first_custom_apn_details_2.Set(::onc::cellular_apn::kPassword,
                                 "test-password-z");
  first_custom_apn_details_2.Set(::onc::cellular_apn::kSource,
                                 ::onc::cellular_apn::kSourceAdmin);

  // Second expected custom APN details for the second network configuration
  base::DictValue second_custom_apn_details_2;
  second_custom_apn_details_2.Set(::onc::cellular_apn::kId, "admin-apn-id-x");
  second_custom_apn_details_2.Set(::onc::cellular_apn::kAccessPointName,
                                  "test-apn-admin-x");
  second_custom_apn_details_2.Set(::onc::cellular_apn::kAuthentication, "");
  second_custom_apn_details_2.Set(::onc::cellular_apn::kUsername,
                                  "test-username-x");
  second_custom_apn_details_2.Set(::onc::cellular_apn::kPassword,
                                  "test-password-x");
  second_custom_apn_details_2.Set(::onc::cellular_apn::kSource,
                                  ::onc::cellular_apn::kSourceAdmin);

  // Add the APN details to the expected custom APN list for the second network
  // configuration
  expected_custom_apns_2.Append(std::move(first_custom_apn_details_2));
  expected_custom_apns_2.Append(std::move(second_custom_apn_details_2));

  // Get the actual APN list from the second network configuration
  const auto* actual_custom_apns_2 =
      network_configs[1].GetDict().FindByDottedPath("Cellular.CustomAPNList");

  // Verify that the actual APN list for the second network configuration
  // matches the expected custom APN list
  EXPECT_TRUE(actual_custom_apns_2->GetList() == expected_custom_apns_2);

  // Expected custom APN list for the third network configuration, which does
  // not have a admin provided custom APN.
  base::ListValue expected_custom_apns_3;

  // The APN should remain unchanged since the admin assigned APN Ids field was
  // not provided.
  base::DictValue only_custom_apn_details_3;
  only_custom_apn_details_3.Set(::onc::cellular_apn::kAccessPointName,
                                "test-apn-3");
  only_custom_apn_details_3.Set(::onc::cellular_apn::kAuthentication, "");
  only_custom_apn_details_3.Set(::onc::cellular_apn::kUsername,
                                "test-username-3");
  only_custom_apn_details_3.Set(::onc::cellular_apn::kPassword,
                                "test-password-3");

  // Add the APN details to the expected custom APN list for the third network
  // configuration
  expected_custom_apns_3.Append(std::move(only_custom_apn_details_3));

  // Get the actual APN list from the third network configuration
  const auto* actual_custom_apns_3 =
      network_configs[2].GetDict().FindByDottedPath("Cellular.CustomAPNList");

  // Verify that the actual APN list for the third network configuration
  // matches the expected custom APN list
  EXPECT_TRUE(actual_custom_apns_3->GetList() == expected_custom_apns_3);

  // Get the actual APN list from the fourth network configuration
  const auto* actual_custom_apns_4 =
      network_configs[3].GetDict().FindByDottedPath("Cellular.CustomAPNList");

  // Verify that the custom APN list is empty if the admin provides an empty
  // list of APN IDs.
  EXPECT_TRUE(actual_custom_apns_4->GetList() == base::ListValue());
}

TEST(ONCUtils,
     ParseAndValidateOncForImport_InvalidPSIMAdminAssignedApnIdsProvided) {
  const auto onc_blob = test_utils::ReadTestData(
      "managed_toplevel_with_invalid_psim_admin_assigned_apn_id_list.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));
}

TEST(ONCUtils, ParseAndValidateOncForImport_PSIMAdminAssignedApnIdsProvided) {
  const auto onc_blob = test_utils::ReadTestData(
      "managed_toplevel_with_psim_admin_assigned_apn_id_list.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));

  // Expected PSIM Admin APN list
  base::ListValue expected_psim_admin_assigned_apns;

  // First expected admin assigned APN details.
  base::DictValue first_psim_admin_assigned_apn;
  first_psim_admin_assigned_apn.Set(::onc::cellular_apn::kId, "admin-apn-id-y");
  first_psim_admin_assigned_apn.Set(::onc::cellular_apn::kAccessPointName,
                                    "test-apn-admin-y");
  first_psim_admin_assigned_apn.Set(::onc::cellular_apn::kAuthentication, "");
  first_psim_admin_assigned_apn.Set(::onc::cellular_apn::kUsername,
                                    "test-username-y");
  first_psim_admin_assigned_apn.Set(::onc::cellular_apn::kPassword,
                                    "test-password-y");
  first_psim_admin_assigned_apn.Set(::onc::cellular_apn::kSource,
                                    ::onc::cellular_apn::kSourceAdmin);

  // Second expected admin assigned APN details.
  base::DictValue second_psim_admin_assigned_apn;
  second_psim_admin_assigned_apn.Set(::onc::cellular_apn::kId,
                                     "admin-apn-id-x");
  second_psim_admin_assigned_apn.Set(::onc::cellular_apn::kAccessPointName,
                                     "test-apn-admin-x");
  second_psim_admin_assigned_apn.Set(::onc::cellular_apn::kAuthentication, "");
  second_psim_admin_assigned_apn.Set(::onc::cellular_apn::kUsername,
                                     "test-username-x");
  second_psim_admin_assigned_apn.Set(::onc::cellular_apn::kPassword,
                                     "test-password-x");
  second_psim_admin_assigned_apn.Set(::onc::cellular_apn::kSource,
                                     ::onc::cellular_apn::kSourceAdmin);

  // Add the APN details to the expected PSIM admin assigned APN list.
  expected_psim_admin_assigned_apns.Append(
      std::move(first_psim_admin_assigned_apn));
  expected_psim_admin_assigned_apns.Append(
      std::move(second_psim_admin_assigned_apn));

  // Get the constructed PSIM admin assigned APN list from the global network
  // configuration
  const auto* actual_psim_admin_assigned_apns =
      global_network_config.FindByDottedPath("PSIMAdminAssignedAPNs");

  // Verify that the constructed PSIM admin assigned APN list matches the
  // expected
  EXPECT_TRUE(actual_psim_admin_assigned_apns->GetList() ==
              expected_psim_admin_assigned_apns);
}

TEST(ONCUtils, ParseAndValidateOncForImport_AdminApnProvidedWithDuplicateIds) {
  const auto onc_blob = test_utils::ReadTestData("duplicate_admin_apn_ids.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_FALSE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY, &network_configs,
      &global_network_config, &certificates));
}

TEST(ONCUtils, ParseAndValidateOncForImport_WithAdvancedOpenVPNSettings) {
  constexpr auto* auth_key =
      "-----BEGIN OpenVPN Static key V1-----\n"
      "83f8e7ccd99be189b4663e18615f9166\n"
      "d885cdea6c8accb0ebf5be304f0b8081\n"
      "5404f2a6574e029815d7a2fb65b83d0c\n"
      "676850714c6a56b23415a78e06aad6b1\n"
      "34900dd512049598382039e4816cb5ff\n"
      "1848532b71af47578c9b4a14b5bca49f\n"
      "99e0ae4dae2f4e5eadfea374aeb8fb1e\n"
      "a6fdf02adc73ea778dfd43d64bf7bc75\n"
      "7779d629498f8c2fbfd32812bfdf6df7\n"
      "8cebafafef3e5496cb13202274f2768a\n"
      "1959bc53d67a70945c4c8c6f34b63327\n"
      "fb60dc84990ffec1243461e0b6310f61\n"
      "e90aee1f11fb6292d6f5fcd7cd508aab\n"
      "50d80f9963589c148cb4b933ec86128d\n"
      "ed77d3fad6005b62f36369e2319f52bd\n"
      "09c6d2e52cce2362a05009dc29b6b39a\n"
      "-----END OpenVPN Static key V1-----\n";
  const auto onc_blob = test_utils::ReadTestData("valid_openvpn_full.onc");
  base::ListValue network_configs;
  base::DictValue global_network_config;
  base::ListValue certificates;

  ASSERT_TRUE(ParseAndValidateOncForImport(
      onc_blob, ::onc::ONCSource::ONC_SOURCE_USER_POLICY, &network_configs,
      &global_network_config, &certificates));

  const auto* open_vpn =
      network_configs[0].GetDict().FindByDottedPath("VPN.OpenVPN");
  ASSERT_NE(open_vpn, nullptr);
  base::DictValue expected;
  expected.Set(::onc::openvpn::kAuth, "MD5");
  expected.Set(::onc::openvpn::kCipher, "AES-192-CBC");
  expected.Set(::onc::openvpn::kCompressionAlgorithm,
               ::onc::openvpn_compression_algorithm::kLzo);
  expected.Set(::onc::openvpn::kTLSAuthContents, auth_key);
  expected.Set(::onc::openvpn::kKeyDirection, "1");
  EXPECT_THAT(open_vpn,
              Pointee(base::test::DictionaryHasValues(std::move(expected))));
}

struct MaskCredentialsTestCase {
  // This field is not a raw_ptr<> because it only ever points to statically-
  // allocated memory that is never freed, so it can't possibly dangle.
  RAW_PTR_EXCLUSION const OncValueSignature* onc_signature;
  const char* onc;
  const char* expected_after_masking;
};

using ONCUtilsMaskCredentialsTest =
    testing::TestWithParam<MaskCredentialsTestCase>;

TEST_P(ONCUtilsMaskCredentialsTest, Test) {
  std::optional<base::Value> onc_value = base::JSONReader::Read(
      GetParam().onc, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  ASSERT_TRUE(onc_value) << "Could not parse " << GetParam().onc;
  std::optional<base::Value> expected_after_masking_value =
      base::JSONReader::Read(GetParam().expected_after_masking,
                             base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  ASSERT_TRUE(expected_after_masking_value)
      << "Could not parse " << GetParam().expected_after_masking;

  base::DictValue masked = MaskCredentialsInOncObject(
      *(GetParam().onc_signature), onc_value->GetDict(), "******");

  EXPECT_EQ(masked, expected_after_masking_value->GetDict());
}

constexpr MaskCredentialsTestCase kMaskCredentialsTestCases[] = {
    // Actual passwords in the L2TP Password field in NetworkConfiguration are
    // masked.
    {&kNetworkConfigurationSignature,
     R"({ "GUID": "guid",
        "VPN": {
          "L2TP": {
            "Username": "some username",
            "Password": "secret_pwd"
          }
        }
      }
   )",
     R"({ "GUID": "guid",
        "VPN": {
          "L2TP": {
            "Username": "some username",
            "Password": "******"
          }
        }
      }
   )"},
    // The ${PASSWORD} variable in the L2TP Password field in
    // NetworkConfiguration is not masked.
    {&kNetworkConfigurationSignature,
     R"({ "GUID": "guid",
        "VPN": {
          "L2TP": {
            "Username": "some username",
            "Password": "${PASSWORD}"
          }
        }
      }
   )",
     R"({ "GUID": "guid",
        "VPN": {
          "L2TP": {
            "Username": "some username",
            "Password": "${PASSWORD}"
          }
        }
      }
   )"},
    // Actual passwords in the EAP Password field in NetworkConfiguration are
    // masked.
    {&kNetworkConfigurationSignature,
     R"({ "GUID": "guid",
        "WiFi": {
          "EAP": {
            "Identity": "some username",
            "Password": "secret_pwd"
          }
        }
      }
   )",
     R"({ "GUID": "guid",
        "WiFi": {
          "EAP": {
            "Identity": "some username",
            "Password": "******"
          }
        }
      }
   )"},
    // The ${PASSWORD} variable in the EAP Password field in
    // NetworkConfiguration is not masked.
    {&kNetworkConfigurationSignature,
     R"({ "GUID": "guid",
        "WiFi": {
          "EAP": {
            "Identity": "some username",
            "Password": "${PASSWORD}"
          }
        }
      }
   )",
     R"({ "GUID": "guid",
        "WiFi": {
          "EAP": {
            "Identity": "some username",
            "Password": "${PASSWORD}"
          }
        }
      }
   )"},
    // The PSK Passphrase is masked no matter if it contains ${PASSWORD} or not.
    {&kNetworkConfigurationSignature,
     R"({ "GUID": "guid",
        "WiFi": {
          "Passphrase": "${PASSWORD}"
        }
      }
   )",
     R"({ "GUID": "guid",
        "WiFi": {
          "Passphrase": "******"
        }
      }
   )"},
};

INSTANTIATE_TEST_SUITE_P(ONCUtilsMaskCredentialsTest,
                         ONCUtilsMaskCredentialsTest,
                         ::testing::ValuesIn(kMaskCredentialsTestCases));

}  // namespace chromeos::onc
