910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/common/permissions/permission_set.h"

#include <stddef.h>

#include <array>
#include <memory>
#include <utility>

#include "base/command_line.h"
#include "base/json/json_file_value_serializer.h"
#include "base/memory/ptr_util.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 "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_test_util.h"
#include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
#include "chrome/grit/generated_resources.h"
#include "components/version_info/version_info.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/permissions/permission_message_provider.h"
#include "extensions/common/permissions/permission_message_test_util.h"
#include "extensions/common/permissions/permission_message_util.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/permissions_info.h"
#include "extensions/common/permissions/socket_permission.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

using extension_test_util::LoadManifest;
using extensions::mojom::APIPermissionID;

namespace extensions {

namespace {

static void AddPattern(URLPatternSet* extent, const std::string& pattern) {
  int schemes = URLPattern::SCHEME_ALL;
  extent->AddPattern(URLPattern(schemes, pattern));
}

size_t IndexOf(const PermissionMessages& warnings, const std::string& warning) {
  std::u16string warning16 = base::ASCIIToUTF16(warning);
  size_t i = 0;
  for (const PermissionMessage& msg : warnings) {
    if (msg.message() == warning16)
      return i;
    ++i;
  }

  return warnings.size();
}

PermissionIDSet MakePermissionIDSet(APIPermissionID id1, APIPermissionID id2) {
  PermissionIDSet set;
  set.insert(id1);
  set.insert(id2);
  return set;
}

PermissionIDSet MakePermissionIDSet(const APIPermissionSet& permissions) {
  PermissionIDSet set;
  for (const APIPermission* permission : permissions)
    set.insert(permission->id());
  return set;
}

std::string PermissionIDsToString(const PermissionIDSet& ids) {
  std::vector<std::string> strs;
  for (const PermissionID& id : ids)
    strs.push_back(base::NumberToString(static_cast<int>(id.id())));
  return base::StringPrintf("[ %s ]", base::JoinString(strs, ", ").c_str());
}

std::string CoalescedPermissionIDsToString(const PermissionMessages& msgs) {
  std::vector<std::string> strs;
  for (const PermissionMessage& msg : msgs)
    strs.push_back(PermissionIDsToString(msg.permissions()));
  return base::JoinString(strs, " ");
}

// Check that the given |permissions| produce a single warning message,
// identified by the set of |expected_ids|.
testing::AssertionResult PermissionSetProducesMessage(
    const PermissionSet& permissions,
    Manifest::Type extension_type,
    const PermissionIDSet& expected_ids) {
  const PermissionMessageProvider* provider = PermissionMessageProvider::Get();

  PermissionMessages msgs = provider->GetPermissionMessages(
      provider->GetAllPermissionIDs(permissions, extension_type));
  if (msgs.size() != 1) {
    return testing::AssertionFailure()
           << "Expected single permission message with IDs "
           << PermissionIDsToString(expected_ids) << " but got " << msgs.size()
           << " messages: " << CoalescedPermissionIDsToString(msgs);
  }
  if (!msgs.front().permissions().Equals(expected_ids)) {
    return testing::AssertionFailure()
           << "Expected permission IDs " << PermissionIDsToString(expected_ids)
           << " but got " << PermissionIDsToString(msgs.front().permissions());
  }

  return testing::AssertionSuccess();
}

}  // namespace

// Tests GetByID.
TEST(PermissionsTest, GetByID) {
  PermissionsInfo* info = PermissionsInfo::GetInstance();
  APIPermissionSet apis = info->GetAllForTest();
  for (const auto* api : apis)
    EXPECT_EQ(api->id(), api->info()->id());
}

// Tests that GetByName works with normal permission names and aliases.
TEST(PermissionsTest, GetByName) {
  PermissionsInfo* info = PermissionsInfo::GetInstance();
  EXPECT_EQ(APIPermissionID::kTab, info->GetByName("tabs")->id());
  EXPECT_EQ(APIPermissionID::kManagement, info->GetByName("management")->id());
  EXPECT_FALSE(info->GetByName("alsdkfjasldkfj"));
}

TEST(PermissionsTest, GetAll) {
  size_t count = 0;
  PermissionsInfo* info = PermissionsInfo::GetInstance();
  APIPermissionSet apis = info->GetAllForTest();
  for (const auto* api : apis) {
    // Make sure only the valid permission IDs get returned.
    EXPECT_NE(APIPermissionID::kInvalid, api->id());
    EXPECT_NE(APIPermissionID::kUnknown, api->id());
    count++;
  }
  EXPECT_EQ(count, info->get_permission_count());
}

TEST(PermissionsTest, GetAllByName) {
  std::set<std::string> names;
  names.insert("background");
  names.insert("management");

  // This is an alias of kTab
  names.insert("windows");

  // This unknown name should get dropped.
  names.insert("sdlkfjasdlkfj");

  APIPermissionSet expected;
  expected.insert(APIPermissionID::kBackground);
  expected.insert(APIPermissionID::kManagement);
  expected.insert(APIPermissionID::kTab);

  EXPECT_EQ(expected,
            PermissionsInfo::GetInstance()->GetAllByNameForTest(names));
}

// Tests that the aliases are properly mapped.
TEST(PermissionsTest, Aliases) {
  PermissionsInfo* info = PermissionsInfo::GetInstance();
  // tabs: tabs, windows
  std::string tabs_name = "tabs";
  EXPECT_EQ(tabs_name, info->GetByID(APIPermissionID::kTab)->name());
  EXPECT_EQ(APIPermissionID::kTab, info->GetByName("tabs")->id());
  EXPECT_EQ(APIPermissionID::kTab, info->GetByName("windows")->id());

  // unlimitedStorage: unlimitedStorage, unlimited_storage
  std::string storage_name = "unlimitedStorage";
  EXPECT_EQ(storage_name,
            info->GetByID(APIPermissionID::kUnlimitedStorage)->name());
  EXPECT_EQ(APIPermissionID::kUnlimitedStorage,
            info->GetByName("unlimitedStorage")->id());
  EXPECT_EQ(APIPermissionID::kUnlimitedStorage,
            info->GetByName("unlimited_storage")->id());
}

TEST(PermissionsTest, EffectiveHostPermissions) {
  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "empty.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_EQ(0u, extension->permissions_data()
                      ->GetEffectiveHostPermissions()
                      .patterns()
                      .size());
    EXPECT_FALSE(
        permissions.HasEffectiveAccessToURL(GURL("http://www.google.com")));
    EXPECT_FALSE(permissions.HasEffectiveAccessToAllHosts());
  }

  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "one_host.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("http://www.google.com")));
    EXPECT_FALSE(
        permissions.HasEffectiveAccessToURL(GURL("https://www.google.com")));
    EXPECT_FALSE(permissions.HasEffectiveAccessToAllHosts());
  }

  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "one_host_wildcard.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_TRUE(permissions.HasEffectiveAccessToURL(GURL("http://google.com")));
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("http://foo.google.com")));
    EXPECT_FALSE(permissions.HasEffectiveAccessToAllHosts());
  }

  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "two_hosts.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("http://www.google.com")));
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("http://www.reddit.com")));
    EXPECT_FALSE(permissions.HasEffectiveAccessToAllHosts());
  }

  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "https_not_considered.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_TRUE(permissions.HasEffectiveAccessToURL(GURL("http://google.com")));
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("https://google.com")));
    EXPECT_FALSE(permissions.HasEffectiveAccessToAllHosts());
  }

  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "two_content_scripts.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_TRUE(permissions.HasEffectiveAccessToURL(GURL("http://google.com")));
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("http://www.reddit.com")));
    EXPECT_TRUE(permissions.HasEffectiveAccessToURL(
        GURL("http://news.ycombinator.com")));
    EXPECT_FALSE(permissions.HasEffectiveAccessToAllHosts());
  }

  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "all_hosts.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_TRUE(permissions.HasEffectiveAccessToURL(GURL("http://test/")));
    EXPECT_FALSE(permissions.HasEffectiveAccessToURL(GURL("https://test/")));
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("http://www.google.com")));
    EXPECT_TRUE(permissions.HasEffectiveAccessToAllHosts());
  }

  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "all_hosts2.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_TRUE(permissions.HasEffectiveAccessToURL(GURL("http://test/")));
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("http://www.google.com")));
    EXPECT_TRUE(permissions.HasEffectiveAccessToAllHosts());
  }

  {
    scoped_refptr<const Extension> extension =
        LoadManifest("effective_host_permissions", "all_hosts3.json");
    const PermissionSet& permissions =
        extension->permissions_data()->active_permissions();
    EXPECT_FALSE(permissions.HasEffectiveAccessToURL(GURL("http://test/")));
    EXPECT_TRUE(permissions.HasEffectiveAccessToURL(GURL("https://test/")));
    EXPECT_TRUE(
        permissions.HasEffectiveAccessToURL(GURL("http://www.google.com")));
    EXPECT_TRUE(permissions.HasEffectiveAccessToAllHosts());
  }
}

TEST(PermissionsTest, ExplicitAccessToOrigin) {
  APIPermissionSet apis;
  ManifestPermissionSet manifest_permissions;
  URLPatternSet explicit_hosts;
  URLPatternSet scriptable_hosts;

  AddPattern(&explicit_hosts, "http://*.google.com/*");
  // The explicit host paths should get set to /*.
  AddPattern(&explicit_hosts, "http://www.example.com/a/particular/path/*");

  PermissionSet perm_set(std::move(apis), std::move(manifest_permissions),
                         std::move(explicit_hosts),
                         std::move(scriptable_hosts));
  ASSERT_TRUE(
      perm_set.HasExplicitAccessToOrigin(GURL("http://www.google.com/")));
  ASSERT_TRUE(
      perm_set.HasExplicitAccessToOrigin(GURL("http://test.google.com/")));
  ASSERT_TRUE(
      perm_set.HasExplicitAccessToOrigin(GURL("http://www.example.com")));
  ASSERT_TRUE(perm_set.HasEffectiveAccessToURL(GURL("http://www.example.com")));
  ASSERT_FALSE(
      perm_set.HasExplicitAccessToOrigin(GURL("http://test.example.com")));
}

TEST(PermissionsTest, CreateUnion) {
  ManifestPermissionSet manifest_permissions;
  APIPermissionSet apis1;
  APIPermissionSet apis2;
  APIPermissionSet expected_apis;

  URLPatternSet explicit_hosts1;
  URLPatternSet explicit_hosts2;
  URLPatternSet expected_explicit_hosts;

  URLPatternSet scriptable_hosts1;
  URLPatternSet scriptable_hosts2;
  URLPatternSet expected_scriptable_hosts;

  URLPatternSet effective_hosts;

  std::unique_ptr<const PermissionSet> set1;
  std::unique_ptr<const PermissionSet> set2;
  std::unique_ptr<const PermissionSet> union_set;

  const APIPermissionInfo* permission_info =
      PermissionsInfo::GetInstance()->GetByID(APIPermissionID::kSocket);
  std::unique_ptr<APIPermission> permission =
      permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("tcp-connect:*.example.com:80");
    list.Append("udp-bind::8080");
    list.Append("udp-send-to::8888");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }

  // Union with an empty set.
  apis1.insert(APIPermissionID::kTab);
  apis1.insert(APIPermissionID::kBackground);
  apis1.insert(permission->Clone());
  expected_apis.insert(APIPermissionID::kTab);
  expected_apis.insert(APIPermissionID::kBackground);
  expected_apis.insert(std::move(permission));

  AddPattern(&explicit_hosts1, "http://*.google.com/*");
  AddPattern(&expected_explicit_hosts, "http://*.google.com/*");
  AddPattern(&effective_hosts, "http://*.google.com/*");

  set1 = std::make_unique<PermissionSet>(
      apis1.Clone(), manifest_permissions.Clone(), explicit_hosts1.Clone(),
      scriptable_hosts1.Clone());
  set2 = std::make_unique<PermissionSet>(
      apis2.Clone(), manifest_permissions.Clone(), explicit_hosts2.Clone(),
      scriptable_hosts2.Clone());
  union_set = PermissionSet::CreateUnion(*set1, *set2);
  EXPECT_TRUE(set1->Contains(*set2));
  EXPECT_TRUE(set1->Contains(*union_set));
  EXPECT_FALSE(set2->Contains(*set1));
  EXPECT_FALSE(set2->Contains(*union_set));
  EXPECT_TRUE(union_set->Contains(*set1));
  EXPECT_TRUE(union_set->Contains(*set2));

  EXPECT_EQ(expected_apis, union_set->apis());
  EXPECT_EQ(expected_explicit_hosts, union_set->explicit_hosts());
  EXPECT_EQ(expected_scriptable_hosts, union_set->scriptable_hosts());
  EXPECT_EQ(expected_explicit_hosts, union_set->effective_hosts());

  // Now use a real second set.
  apis2.insert(APIPermissionID::kTab);
  apis2.insert(APIPermissionID::kProxy);
  apis2.insert(APIPermissionID::kClipboardWrite);

  permission = permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("tcp-connect:*.example.com:80");
    list.Append("udp-send-to::8899");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }
  apis2.insert(std::move(permission));

  expected_apis.insert(APIPermissionID::kTab);
  expected_apis.insert(APIPermissionID::kProxy);
  expected_apis.insert(APIPermissionID::kClipboardWrite);

  permission = permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("tcp-connect:*.example.com:80");
    list.Append("udp-bind::8080");
    list.Append("udp-send-to::8888");
    list.Append("udp-send-to::8899");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }
  // Insert a new permission socket permisssion which will replace the old one.
  expected_apis.insert(std::move(permission));

  AddPattern(&explicit_hosts2, "http://*.example.com/*");
  AddPattern(&scriptable_hosts2, "http://*.google.com/*");
  AddPattern(&expected_explicit_hosts, "http://*.example.com/*");
  AddPattern(&expected_scriptable_hosts, "http://*.google.com/*");

  effective_hosts =
      URLPatternSet::CreateUnion(explicit_hosts2, scriptable_hosts2);

  set2 = std::make_unique<PermissionSet>(
      apis2.Clone(), manifest_permissions.Clone(), explicit_hosts2.Clone(),
      scriptable_hosts2.Clone());
  union_set = PermissionSet::CreateUnion(*set1, *set2);

  EXPECT_FALSE(set1->Contains(*set2));
  EXPECT_FALSE(set1->Contains(*union_set));
  EXPECT_FALSE(set2->Contains(*set1));
  EXPECT_FALSE(set2->Contains(*union_set));
  EXPECT_TRUE(union_set->Contains(*set1));
  EXPECT_TRUE(union_set->Contains(*set2));

  EXPECT_TRUE(union_set->HasEffectiveAccessToAllHosts());
  EXPECT_EQ(expected_apis, union_set->apis());
  EXPECT_EQ(expected_explicit_hosts, union_set->explicit_hosts());
  EXPECT_EQ(expected_scriptable_hosts, union_set->scriptable_hosts());
  EXPECT_EQ(effective_hosts, union_set->effective_hosts());
}

TEST(PermissionsTest, CreateIntersection) {
  ManifestPermissionSet manifest_permissions;
  APIPermissionSet apis1;
  APIPermissionSet apis2;
  APIPermissionSet expected_apis;

  URLPatternSet explicit_hosts1;
  URLPatternSet explicit_hosts2;
  URLPatternSet expected_explicit_hosts;

  URLPatternSet scriptable_hosts1;
  URLPatternSet scriptable_hosts2;
  URLPatternSet expected_scriptable_hosts;

  URLPatternSet effective_hosts;

  std::unique_ptr<const PermissionSet> set1;
  std::unique_ptr<const PermissionSet> set2;
  std::unique_ptr<const PermissionSet> new_set;

  const APIPermissionInfo* permission_info =
      PermissionsInfo::GetInstance()->GetByID(APIPermissionID::kSocket);

  // Intersection with an empty set.
  apis1.insert(APIPermissionID::kTab);
  apis1.insert(APIPermissionID::kBackground);
  std::unique_ptr<APIPermission> permission =
      permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("tcp-connect:*.example.com:80");
    list.Append("udp-bind::8080");
    list.Append("udp-send-to::8888");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }
  apis1.insert(std::move(permission));

  AddPattern(&explicit_hosts1, "http://*.google.com/*");
  AddPattern(&scriptable_hosts1, "http://www.reddit.com/*");

  set1 = std::make_unique<PermissionSet>(
      apis1.Clone(), manifest_permissions.Clone(), explicit_hosts1.Clone(),
      scriptable_hosts1.Clone());
  set2 = std::make_unique<PermissionSet>(
      apis2.Clone(), manifest_permissions.Clone(), explicit_hosts2.Clone(),
      scriptable_hosts2.Clone());
  new_set = PermissionSet::CreateIntersection(*set1, *set2);
  EXPECT_TRUE(set1->Contains(*new_set));
  EXPECT_TRUE(set2->Contains(*new_set));
  EXPECT_TRUE(set1->Contains(*set2));
  EXPECT_FALSE(set2->Contains(*set1));
  EXPECT_FALSE(new_set->Contains(*set1));
  EXPECT_TRUE(new_set->Contains(*set2));

  EXPECT_TRUE(new_set->IsEmpty());
  EXPECT_EQ(expected_apis, new_set->apis());
  EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts());
  EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts());
  EXPECT_EQ(expected_explicit_hosts, new_set->effective_hosts());

  // Now use a real second set.
  apis2.insert(APIPermissionID::kTab);
  apis2.insert(APIPermissionID::kProxy);
  apis2.insert(APIPermissionID::kClipboardWrite);
  permission = permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("udp-bind::8080");
    list.Append("udp-send-to::8888");
    list.Append("udp-send-to::8899");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }
  apis2.insert(std::move(permission));

  expected_apis.insert(APIPermissionID::kTab);
  permission = permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("udp-bind::8080");
    list.Append("udp-send-to::8888");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }
  expected_apis.insert(std::move(permission));

  AddPattern(&explicit_hosts2, "http://*.example.com/*");
  AddPattern(&explicit_hosts2, "http://*.google.com/*");
  AddPattern(&scriptable_hosts2, "http://*.google.com/*");
  AddPattern(&expected_explicit_hosts, "http://*.google.com/*");

  effective_hosts.ClearPatterns();
  AddPattern(&effective_hosts, "http://*.google.com/*");

  set2 = std::make_unique<PermissionSet>(
      apis2.Clone(), manifest_permissions.Clone(), explicit_hosts2.Clone(),
      scriptable_hosts2.Clone());
  new_set = PermissionSet::CreateIntersection(*set1, *set2);

  EXPECT_TRUE(set1->Contains(*new_set));
  EXPECT_TRUE(set2->Contains(*new_set));
  EXPECT_FALSE(set1->Contains(*set2));
  EXPECT_FALSE(set2->Contains(*set1));
  EXPECT_FALSE(new_set->Contains(*set1));
  EXPECT_FALSE(new_set->Contains(*set2));

  EXPECT_FALSE(new_set->HasEffectiveAccessToAllHosts());
  EXPECT_EQ(expected_apis, new_set->apis());
  EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts());
  EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts());
  EXPECT_EQ(effective_hosts, new_set->effective_hosts());
}

TEST(PermissionsTest, CreateDifference) {
  ManifestPermissionSet manifest_permissions;
  APIPermissionSet apis1;
  APIPermissionSet apis2;
  APIPermissionSet expected_apis;

  URLPatternSet explicit_hosts1;
  URLPatternSet explicit_hosts2;
  URLPatternSet expected_explicit_hosts;

  URLPatternSet scriptable_hosts1;
  URLPatternSet scriptable_hosts2;
  URLPatternSet expected_scriptable_hosts;

  URLPatternSet effective_hosts;

  std::unique_ptr<const PermissionSet> set1;
  std::unique_ptr<const PermissionSet> set2;
  std::unique_ptr<const PermissionSet> new_set;

  const APIPermissionInfo* permission_info =
      PermissionsInfo::GetInstance()->GetByID(APIPermissionID::kSocket);

  // Difference with an empty set.
  apis1.insert(APIPermissionID::kTab);
  apis1.insert(APIPermissionID::kBackground);
  std::unique_ptr<APIPermission> permission =
      permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("tcp-connect:*.example.com:80");
    list.Append("udp-bind::8080");
    list.Append("udp-send-to::8888");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }
  apis1.insert(std::move(permission));

  AddPattern(&explicit_hosts1, "http://*.google.com/*");
  AddPattern(&scriptable_hosts1, "http://www.reddit.com/*");

  set1 = std::make_unique<PermissionSet>(
      apis1.Clone(), manifest_permissions.Clone(), explicit_hosts1.Clone(),
      scriptable_hosts1.Clone());
  set2 = std::make_unique<PermissionSet>(
      apis2.Clone(), manifest_permissions.Clone(), explicit_hosts2.Clone(),
      scriptable_hosts2.Clone());
  new_set = PermissionSet::CreateDifference(*set1, *set2);
  EXPECT_EQ(*set1, *new_set);

  // Now use a real second set.
  apis2.insert(APIPermissionID::kTab);
  apis2.insert(APIPermissionID::kProxy);
  apis2.insert(APIPermissionID::kClipboardWrite);
  permission = permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("tcp-connect:*.example.com:80");
    list.Append("udp-send-to::8899");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }
  apis2.insert(std::move(permission));

  expected_apis.insert(APIPermissionID::kBackground);
  permission = permission_info->CreateAPIPermission();
  {
    base::Value::List list;
    list.Append("udp-bind::8080");
    list.Append("udp-send-to::8888");
    base::Value value(std::move(list));
    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
  }
  expected_apis.insert(std::move(permission));

  AddPattern(&explicit_hosts2, "http://*.example.com/*");
  AddPattern(&explicit_hosts2, "http://*.google.com/*");
  AddPattern(&scriptable_hosts2, "http://*.google.com/*");
  AddPattern(&expected_scriptable_hosts, "http://www.reddit.com/*");

  effective_hosts.ClearPatterns();
  AddPattern(&effective_hosts, "http://www.reddit.com/*");

  set2 = std::make_unique<PermissionSet>(
      apis2.Clone(), manifest_permissions.Clone(), explicit_hosts2.Clone(),
      scriptable_hosts2.Clone());
  new_set = PermissionSet::CreateDifference(*set1, *set2);

  EXPECT_TRUE(set1->Contains(*new_set));
  EXPECT_FALSE(set2->Contains(*new_set));

  EXPECT_FALSE(new_set->HasEffectiveAccessToAllHosts());
  EXPECT_EQ(expected_apis, new_set->apis());
  EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts());
  EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts());
  EXPECT_EQ(effective_hosts, new_set->effective_hosts());

  // |set3| = |set1| - |set2| --> |set3| intersect |set2| == empty_set
  set1 = PermissionSet::CreateIntersection(*new_set, *set2);
  EXPECT_TRUE(set1->IsEmpty());
}

TEST(PermissionsTest, IsPrivilegeIncrease) {
  struct Tests {
    const char* base_name;
    bool expect_increase;
  };
  const auto kTests = std::to_array<Tests>({
      {"allhosts1", false},     // all -> all
      {"allhosts2", false},     // all -> one
      {"allhosts3", true},      // one -> all
      {"hosts1", false},        // http://a,http://b -> http://a,http://b
      {"hosts2", true},         // http://a,http://b -> https://a,http://*.b
      {"hosts3", false},        // http://a,http://b -> http://a
      {"hosts4", true},         // http://a -> http://a,http://b
      {"hosts5", false},        // http://a,b,c -> http://a,b,c + https://a,b,c
      {"hosts6", false},        // http://a.com -> http://a.com + http://a.co.uk
      {"permissions1", false},  // tabs -> tabs
      {"permissions2", true},   // tabs -> tabs,bookmarks
      {"permissions3", false},  // http://*/* -> http://*/*,tabs
      {"permissions5", true},   // bookmarks -> bookmarks,history
      {"equivalent_warnings", false},  // tabs --> tabs, webNavigation

      // The plugins manifest key is deprecated and doesn't correspond to any
      // permissions now.
      {"permissions4", true},  // plugin -> plugin,tabs
      {"plugin1", false},      // plugin -> plugin
      {"plugin2", false},      // plugin -> none
      {"plugin3", false},      // none -> plugin

      {"storage", false},       // none -> storage
      {"notifications", true},  // none -> notifications
#if BUILDFLAG(ENABLE_PLATFORM_APPS)
      // All of the below are platform app permissions.
      {"platformapp1", false},      // host permissions for platform apps
      {"platformapp2", true},       // API permissions for platform apps
      {"media_galleries1", true},   // all -> read|all
      {"media_galleries2", true},   // read|all -> read|delete|copyTo|all
      {"media_galleries3", true},   // all -> read|delete|all
      {"media_galleries4", false},  // read|all -> all
      {"media_galleries5", false},  // read|copyTo|delete|all -> read|all
      {"media_galleries6", false},  // read|all -> read|all
      {"media_galleries7", true},   // read|delete|all -> read|copyTo|delete|all
      {"sockets1", true},           // none -> tcp:*:*
      {"sockets2", false},          // tcp:*:* -> tcp:*:*
      {"sockets3", true},           // tcp:a.com:80 -> tcp:*:*
#endif
  });

  for (size_t i = 0; i < std::size(kTests); ++i) {
    scoped_refptr<Extension> old_extension(
        LoadManifest("allow_silent_upgrade",
                     std::string(kTests[i].base_name) + "_old.json"));
    scoped_refptr<Extension> new_extension(
        LoadManifest("allow_silent_upgrade",
                     std::string(kTests[i].base_name) + "_new.json"));

    EXPECT_TRUE(new_extension.get()) << kTests[i].base_name << "_new.json";
    if (!new_extension.get())
      continue;

    const PermissionSet& old_p =
        old_extension->permissions_data()->active_permissions();
    const PermissionSet& new_p =
        new_extension->permissions_data()->active_permissions();
    Manifest::Type extension_type = old_extension->GetType();

    bool increased = PermissionMessageProvider::Get()->IsPrivilegeIncrease(
        old_p, new_p, extension_type);
    EXPECT_EQ(kTests[i].expect_increase, increased) << kTests[i].base_name;
  }
}

// Tests that swapping out a permission for a less powerful one is not
// considered a privilege increase.
// Regression test for https://crbug.com/841938.
TEST(PermissionsTest,
     IsNotPrivilegeIncreaseWhenSwitchingForLowerPrivilegePermission) {
  APIPermissionSet apis1;
  apis1.insert(APIPermissionID::kHistory);
  PermissionSet permissions1(apis1.Clone(), ManifestPermissionSet(),
                             URLPatternSet(), URLPatternSet());

  APIPermissionSet apis2;
  apis2.insert(APIPermissionID::kTopSites);
  PermissionSet permissions2(apis2.Clone(), ManifestPermissionSet(),
                             URLPatternSet(), URLPatternSet());

  EXPECT_FALSE(PermissionMessageProvider::Get()->IsPrivilegeIncrease(
      permissions1, permissions2, Manifest::TYPE_EXTENSION));
}

TEST(PermissionsTest, PermissionMessages) {
  // Ensure that all permissions that needs to show install UI actually have
  // strings associated with them. Permissions that require support for
  // platform apps are ifdef'd out.
  APIPermissionSet skip;

  // These are considered "nuisance" or "trivial" permissions that don't need
  // a prompt.
  skip.insert(APIPermissionID::kActiveTab);
  skip.insert(APIPermissionID::kAlarms);
  skip.insert(APIPermissionID::kAlphaEnabled);
  skip.insert(APIPermissionID::kAlwaysOnTopWindows);
  skip.insert(APIPermissionID::kAppView);
  skip.insert(APIPermissionID::kAudio);
  skip.insert(APIPermissionID::kBrowsingData);
  skip.insert(APIPermissionID::kCommandsAccessibility);
  skip.insert(APIPermissionID::kContextMenus);
  skip.insert(APIPermissionID::kDiagnostics);
  skip.insert(APIPermissionID::kDns);
  skip.insert(APIPermissionID::kDownloadsShelf);
  skip.insert(APIPermissionID::kDownloadsUi);
  skip.insert(APIPermissionID::kFontSettings);
  skip.insert(APIPermissionID::kFullscreen);
  skip.insert(APIPermissionID::kGcm);
  skip.insert(APIPermissionID::kIdle);
  skip.insert(APIPermissionID::kImeWindowEnabled);
  skip.insert(APIPermissionID::kIdltest);
  skip.insert(APIPermissionID::kLoginState);
  skip.insert(APIPermissionID::kOffscreen);
  skip.insert(APIPermissionID::kOverrideEscFullscreen);
#if BUILDFLAG(ENABLE_PLATFORM_APPS)
  skip.insert(APIPermissionID::kPointerLock);
#endif
  skip.insert(APIPermissionID::kPower);
  skip.insert(APIPermissionID::kPrinterProvider);
  skip.insert(APIPermissionID::kSearch);
  skip.insert(APIPermissionID::kSessions);
  skip.insert(APIPermissionID::kSidePanel);
  skip.insert(APIPermissionID::kStorage);
  skip.insert(APIPermissionID::kSystemCpu);
  skip.insert(APIPermissionID::kSystemDisplay);
  skip.insert(APIPermissionID::kSystemMemory);
  skip.insert(APIPermissionID::kSystemNetwork);
  skip.insert(APIPermissionID::kTts);
  skip.insert(APIPermissionID::kUnlimitedStorage);
  skip.insert(APIPermissionID::kWebcamPrivate);
  skip.insert(APIPermissionID::kWebView);
  skip.insert(APIPermissionID::kWindowShape);

  // TODO(erikkay) add a string for this permission.
  skip.insert(APIPermissionID::kBackground);

  skip.insert(APIPermissionID::kClipboard);

  // The cookie permission does nothing unless you have associated host
  // permissions.
  skip.insert(APIPermissionID::kCookie);

  // These are warned as part of host permission checks.
  skip.insert(APIPermissionID::kDeclarativeContent);
  skip.insert(APIPermissionID::kPageCapture);
  skip.insert(APIPermissionID::kProxy);
  skip.insert(APIPermissionID::kScripting);
  skip.insert(APIPermissionID::kTabCapture);
  skip.insert(APIPermissionID::kUserScripts);
  skip.insert(APIPermissionID::kWebRequest);
  skip.insert(APIPermissionID::kWebRequestBlocking);
  skip.insert(APIPermissionID::kWebRequestAuthProvider);
  skip.insert(APIPermissionID::kDeclarativeNetRequestWithHostAccess);

  // This permission requires explicit user action (context menu handler)
  // so we won't prompt for it for now.
  skip.insert(APIPermissionID::kFileBrowserHandler);

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
  // These permissions require explicit user action (configuration dialog)
  // so we don't prompt for them at install time.
  skip.insert(APIPermissionID::kMediaGalleries);
#endif

  // If you've turned on the experimental command-line flag, we don't need
  // to warn you further.
  skip.insert(APIPermissionID::kExperimental);

  // The Experimental AI Data API is gated on commandline switches, in
  // addition to the permission in the manifest. If you've turned on the
  // experimental AI Data command-line flag, we don't need to warn you further.
  skip.insert(APIPermissionID::kExperimentalAiData);

  // The Experimental Actor API is gated on commandline switches, in
  // addition to the permission in the manifest. If you've turned on the
  // experimental Actor command-line flag, we don't need to warn you further.
  skip.insert(APIPermissionID::kExperimentalActor);

  // The Identity API has its own server-driven permission prompts.
  skip.insert(APIPermissionID::kIdentity);

  // These are private.
  skip.insert(APIPermissionID::kAccessibilityPrivate);
  skip.insert(APIPermissionID::kAccessibilityServicePrivate);
#if BUILDFLAG(ENABLE_PLATFORM_APPS)
  skip.insert(APIPermissionID::kArcAppsPrivate);
#endif
  skip.insert(APIPermissionID::kAutoTestPrivate);
  skip.insert(APIPermissionID::kBrailleDisplayPrivate);
  skip.insert(APIPermissionID::kCecPrivate);
  skip.insert(APIPermissionID::kChromeosInfoPrivate);
  skip.insert(APIPermissionID::kCommandLinePrivate);
  skip.insert(APIPermissionID::kCrashReportPrivate);
  skip.insert(APIPermissionID::kDeveloperPrivate);
  skip.insert(APIPermissionID::kEchoPrivate);
  skip.insert(APIPermissionID::kEnterprisePlatformKeysPrivate);
  skip.insert(APIPermissionID::kFeedbackPrivate);
  skip.insert(APIPermissionID::kFileManagerPrivate);
#if BUILDFLAG(ENABLE_PLATFORM_APPS)
  skip.insert(APIPermissionID::kFirstRunPrivate);
#endif
  skip.insert(APIPermissionID::kImageLoaderPrivate);
  skip.insert(APIPermissionID::kInputMethodPrivate);
  skip.insert(APIPermissionID::kLanguageSettingsPrivate);
  skip.insert(APIPermissionID::kLockWindowFullscreenPrivate);
  skip.insert(APIPermissionID::kMediaPlayerPrivate);
  skip.insert(APIPermissionID::kMediaPerceptionPrivate);
  skip.insert(APIPermissionID::kMetricsPrivate);
  skip.insert(APIPermissionID::kPdfViewerPrivate);
  skip.insert(APIPermissionID::kImageWriterPrivate);
  skip.insert(APIPermissionID::kResourcesPrivate);
  skip.insert(APIPermissionID::kSafeBrowsingPrivate);
  skip.insert(APIPermissionID::kSmartCardProviderPrivate);
  skip.insert(APIPermissionID::kSystemPrivate);
  skip.insert(APIPermissionID::kTabCaptureForTab);
  skip.insert(APIPermissionID::kTerminalPrivate);
  skip.insert(APIPermissionID::kVirtualKeyboardPrivate);
  skip.insert(APIPermissionID::kWebrtcAudioPrivate);
  skip.insert(APIPermissionID::kWebrtcDesktopCapturePrivate);
  skip.insert(APIPermissionID::kWebrtcLoggingPrivate);
  skip.insert(APIPermissionID::kWebrtcLoggingPrivateAudioDebug);
  skip.insert(APIPermissionID::kWebstorePrivate);
  skip.insert(APIPermissionID::kWmDesksPrivate);
  skip.insert(APIPermissionID::kSystemLog);
  skip.insert(APIPermissionID::kOdfsConfigPrivate);

  // Warned as part of host permissions.
  skip.insert(APIPermissionID::kDevtools);

  // Platform apps. Most platform app permissions are not yet behind the build
  // flag, which is being gradually added.
#if BUILDFLAG(ENABLE_PLATFORM_APPS)
  skip.insert(APIPermissionID::kBrowser);
#endif
  skip.insert(APIPermissionID::kHid);
  skip.insert(APIPermissionID::kFileSystem);
  skip.insert(APIPermissionID::kFileSystemProvider);
  skip.insert(APIPermissionID::kFileSystemRequestFileSystem);
  skip.insert(APIPermissionID::kFileSystemRetainEntries);
  skip.insert(APIPermissionID::kFileSystemWrite);
  skip.insert(APIPermissionID::kSocket);
  skip.insert(APIPermissionID::kUsb);
  skip.insert(APIPermissionID::kVirtualKeyboard);

  // We already have a generic message for declaring externally_connectable.
  skip.insert(APIPermissionID::kDeprecated_ExternallyConnectableAllUrls);

  const PermissionMessageProvider* provider = PermissionMessageProvider::Get();
  PermissionsInfo* info = PermissionsInfo::GetInstance();
  APIPermissionSet permissions = info->GetAllForTest();
  for (const auto* permission : permissions) {
    const APIPermissionInfo* permission_info = permission->info();
    EXPECT_TRUE(permission_info);

    PermissionIDSet id;
    id.insert(permission_info->id());
    bool has_message = !provider->GetPermissionMessages(id).empty();
    bool should_have_message = !skip.count(permission->id());
    EXPECT_EQ(should_have_message, has_message) << permission_info->name();
  }
}

TEST(PermissionsTest, FileSystemPermissionMessages) {
  APIPermissionSet api_permissions;
  api_permissions.insert(APIPermissionID::kFileSystemWrite);
  api_permissions.insert(APIPermissionID::kFileSystemDirectory);
  PermissionSet permissions(api_permissions.Clone(), ManifestPermissionSet(),
                            URLPatternSet(), URLPatternSet());
  EXPECT_TRUE(
      PermissionSetProducesMessage(permissions, Manifest::TYPE_PLATFORM_APP,
                                   MakePermissionIDSet(api_permissions)));
}

TEST(PermissionsTest, HiddenFileSystemPermissionMessages) {
  APIPermissionSet api_permissions;
  api_permissions.insert(APIPermissionID::kFileSystemWrite);
  api_permissions.insert(APIPermissionID::kFileSystemDirectory);
  PermissionSet permissions(api_permissions.Clone(), ManifestPermissionSet(),
                            URLPatternSet(), URLPatternSet());
  EXPECT_TRUE(
      PermissionSetProducesMessage(permissions, Manifest::TYPE_PLATFORM_APP,
                                   MakePermissionIDSet(api_permissions)));
}

TEST(PermissionsTest, SuppressedPermissionMessages) {
  {
    // Tabs warning suppresses favicon warning.
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kTab);
    URLPatternSet hosts;
    hosts.AddPattern(URLPattern(URLPattern::SCHEME_CHROMEUI,
                                "chrome://favicon/"));
    PermissionSet permissions(std::move(api_permissions),
                              ManifestPermissionSet(), std::move(hosts),
                              URLPatternSet());
    EXPECT_TRUE(PermissionSetProducesMessage(
        permissions, Manifest::TYPE_EXTENSION,
        MakePermissionIDSet(APIPermissionID::kTab, APIPermissionID::kFavicon)));
  }
  {
    // History warning suppresses favicon warning.
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kHistory);
    URLPatternSet hosts;
    hosts.AddPattern(URLPattern(URLPattern::SCHEME_CHROMEUI,
                                "chrome://favicon/"));
    PermissionSet permissions(api_permissions.Clone(), ManifestPermissionSet(),
                              std::move(hosts), URLPatternSet());
    EXPECT_TRUE(PermissionSetProducesMessage(
        permissions, Manifest::TYPE_EXTENSION,
        MakePermissionIDSet(APIPermissionID::kHistory,
                            APIPermissionID::kFavicon)));
  }
  {
    // All sites warning suppresses tabs warning.
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kTab);
    URLPatternSet hosts;
    hosts.AddPattern(URLPattern(URLPattern::SCHEME_HTTP, "*://*/*"));
    PermissionSet permissions(std::move(api_permissions),
                              ManifestPermissionSet(), std::move(hosts),
                              URLPatternSet());
    EXPECT_TRUE(PermissionSetProducesMessage(
        permissions, Manifest::TYPE_EXTENSION,
        MakePermissionIDSet(APIPermissionID::kHostsAll,
                            APIPermissionID::kTab)));
  }
  {
    // All sites warning suppresses topSites warning.
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kTopSites);
    URLPatternSet hosts;
    hosts.AddPattern(URLPattern(URLPattern::SCHEME_HTTP, "*://*/*"));
    PermissionSet permissions(std::move(api_permissions),
                              ManifestPermissionSet(), std::move(hosts),
                              URLPatternSet());
    EXPECT_TRUE(PermissionSetProducesMessage(
        permissions, Manifest::TYPE_EXTENSION,
        MakePermissionIDSet(APIPermissionID::kHostsAll,
                            APIPermissionID::kTopSites)));
  }
  {
    // All sites warning suppresses declarativeWebRequest warning.
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kDeclarativeWebRequest);
    URLPatternSet hosts;
    hosts.AddPattern(URLPattern(URLPattern::SCHEME_HTTP, "*://*/*"));
    PermissionSet permissions(std::move(api_permissions),
                              ManifestPermissionSet(), std::move(hosts),
                              URLPatternSet());
    EXPECT_TRUE(PermissionSetProducesMessage(
        permissions, Manifest::TYPE_EXTENSION,
        MakePermissionIDSet(APIPermissionID::kHostsAll,
                            APIPermissionID::kDeclarativeWebRequest)));
  }
  {
    // BrowsingHistory warning suppresses all history read/write warnings.
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kHistory);
    api_permissions.insert(APIPermissionID::kTab);
    api_permissions.insert(APIPermissionID::kTopSites);
    api_permissions.insert(APIPermissionID::kProcesses);
    api_permissions.insert(APIPermissionID::kWebNavigation);
    PermissionSet permissions(api_permissions.Clone(), ManifestPermissionSet(),
                              URLPatternSet(), URLPatternSet());
    EXPECT_TRUE(
        PermissionSetProducesMessage(permissions, Manifest::TYPE_EXTENSION,
                                     MakePermissionIDSet(api_permissions)));
  }
  {
    // Tabs warning suppresses all read-only history warnings.
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kTab);
    api_permissions.insert(APIPermissionID::kTopSites);
    api_permissions.insert(APIPermissionID::kProcesses);
    api_permissions.insert(APIPermissionID::kWebNavigation);
    PermissionSet permissions(api_permissions.Clone(), ManifestPermissionSet(),
                              URLPatternSet(), URLPatternSet());
    EXPECT_TRUE(
        PermissionSetProducesMessage(permissions, Manifest::TYPE_EXTENSION,
                                     MakePermissionIDSet(api_permissions)));
  }
}

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
// "serial" is a platform app permission and not supported on desktop Android.
TEST(PermissionsTest, AccessToDevicesMessages) {
  {
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kSerial);
    PermissionSet permissions(std::move(api_permissions),
                              ManifestPermissionSet(), URLPatternSet(),
                              URLPatternSet());
    EXPECT_TRUE(VerifyOnePermissionMessage(
        permissions, Manifest::TYPE_EXTENSION,
        l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_SERIAL)));
  }
  {
    // Testing that multiple permissions will show the one message.
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kSerial);
    api_permissions.insert(APIPermissionID::kSerial);
    PermissionSet permissions(std::move(api_permissions),
                              ManifestPermissionSet(), URLPatternSet(),
                              URLPatternSet());
    EXPECT_TRUE(VerifyOnePermissionMessage(
        permissions, Manifest::TYPE_EXTENSION,
        l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_SERIAL)));
  }
  {
    scoped_refptr<Extension> extension =
        LoadManifest("permissions", "access_to_devices_bluetooth.json");
    PermissionSet& set = const_cast<PermissionSet&>(
        extension->permissions_data()->active_permissions());
    EXPECT_TRUE(VerifyOnePermissionMessage(
        set, extension->GetType(),
        l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH)));

    // Test Bluetooth and Serial
    set.apis_.insert(APIPermissionID::kSerial);
    EXPECT_TRUE(VerifyOnePermissionMessage(
        set, extension->GetType(),
        l10n_util::GetStringUTF16(
            IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH_SERIAL)));
  }
}
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

TEST(PermissionsTest, MergedFileSystemPermissionComparison) {
  APIPermissionSet write_api_permissions;
  write_api_permissions.insert(APIPermissionID::kFileSystemWrite);
  PermissionSet write_permissions(write_api_permissions.Clone(),
                                  ManifestPermissionSet(), URLPatternSet(),
                                  URLPatternSet());

  APIPermissionSet directory_api_permissions;
  directory_api_permissions.insert(APIPermissionID::kFileSystemDirectory);
  PermissionSet directory_permissions(directory_api_permissions.Clone(),
                                      ManifestPermissionSet(), URLPatternSet(),
                                      URLPatternSet());

  APIPermissionSet write_directory_api_permissions;
  write_directory_api_permissions.insert(APIPermissionID::kFileSystemWrite);
  write_directory_api_permissions.insert(APIPermissionID::kFileSystemDirectory);
  PermissionSet write_directory_permissions(
      write_directory_api_permissions.Clone(), ManifestPermissionSet(),
      URLPatternSet(), URLPatternSet());

  const PermissionMessageProvider* provider = PermissionMessageProvider::Get();
  EXPECT_FALSE(provider->IsPrivilegeIncrease(write_directory_permissions,
                                             write_permissions,
                                             Manifest::TYPE_PLATFORM_APP));
  EXPECT_FALSE(provider->IsPrivilegeIncrease(write_directory_permissions,
                                             directory_permissions,
                                             Manifest::TYPE_PLATFORM_APP));
  EXPECT_TRUE(provider->IsPrivilegeIncrease(write_permissions,
                                            write_directory_permissions,
                                            Manifest::TYPE_PLATFORM_APP));
  EXPECT_TRUE(provider->IsPrivilegeIncrease(directory_permissions,
                                            write_directory_permissions,
                                            Manifest::TYPE_PLATFORM_APP));
  // Tricky case: going from kFileSystemWrite to kFileSystemDirectory (or vice
  // versa). A warning is only shown if *both* kFileSystemWrite and
  // kFileSystemDirectory are present. Even though kFileSystemWrite is not in
  // the new set of permissions, it will still be a granted permission.
  // Therefore, we should consider this a privilege increase.
  EXPECT_TRUE(provider->IsPrivilegeIncrease(
      write_permissions, directory_permissions, Manifest::TYPE_PLATFORM_APP));
  EXPECT_TRUE(provider->IsPrivilegeIncrease(
      directory_permissions, write_permissions, Manifest::TYPE_PLATFORM_APP));
}

TEST(PermissionsTest, GetWarningMessages_ManyHosts) {
  scoped_refptr<Extension> extension =
      LoadManifest("permissions", "many-hosts.json");
  EXPECT_TRUE(VerifyOnePermissionMessage(
      extension->permissions_data(),
      "Read and change your data on encrypted.google.com and www.google.com"));
}

TEST(PermissionsTest, GetWarningMessages_AudioVideo) {
  const std::string kAudio("Use your microphone");
  const std::string kVideo("Use your camera");
  const std::string kBoth("Use your microphone and camera");

  // Both audio and video present.
  scoped_refptr<Extension> extension =
      LoadManifest("permissions", "audio-video.json");
  const PermissionMessageProvider* provider = PermissionMessageProvider::Get();
  PermissionSet& set = const_cast<PermissionSet&>(
      extension->permissions_data()->active_permissions());
  EXPECT_FALSE(VerifyHasPermissionMessage(set, extension->GetType(), kAudio));
  EXPECT_FALSE(VerifyHasPermissionMessage(set, extension->GetType(), kVideo));
  EXPECT_TRUE(VerifyHasPermissionMessage(set, extension->GetType(), kBoth));
  PermissionMessages warnings = provider->GetPermissionMessages(
      provider->GetAllPermissionIDs(set, extension->GetType()));
  size_t combined_index = IndexOf(warnings, kBoth);
  size_t combined_size = warnings.size();

  // Just audio present.
  set.apis_.erase(APIPermissionID::kVideoCapture);
  EXPECT_TRUE(VerifyHasPermissionMessage(set, extension->GetType(), kAudio));
  EXPECT_FALSE(VerifyHasPermissionMessage(set, extension->GetType(), kVideo));
  EXPECT_FALSE(VerifyHasPermissionMessage(set, extension->GetType(), kBoth));
  PermissionMessages warnings2 = provider->GetPermissionMessages(
      provider->GetAllPermissionIDs(set, extension->GetType()));
  EXPECT_EQ(combined_size, warnings2.size());
  EXPECT_EQ(combined_index, IndexOf(warnings2, kAudio));

  // Just video present.
  set.apis_.erase(APIPermissionID::kAudioCapture);
  set.apis_.insert(APIPermissionID::kVideoCapture);
  EXPECT_FALSE(VerifyHasPermissionMessage(set, extension->GetType(), kAudio));
  EXPECT_TRUE(VerifyHasPermissionMessage(set, extension->GetType(), kVideo));
  EXPECT_FALSE(VerifyHasPermissionMessage(set, extension->GetType(), kBoth));
  PermissionMessages warnings3 = provider->GetPermissionMessages(
      provider->GetAllPermissionIDs(set, extension->GetType()));
  EXPECT_EQ(combined_size, warnings3.size());
  EXPECT_EQ(combined_index, IndexOf(warnings3, kVideo));
}

TEST(PermissionsTest, GetWarningMessages_CombinedSessions) {
  {
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kTab);
    api_permissions.insert(APIPermissionID::kTopSites);
    api_permissions.insert(APIPermissionID::kProcesses);
    api_permissions.insert(APIPermissionID::kWebNavigation);
    api_permissions.insert(APIPermissionID::kSessions);
    PermissionSet permissions(std::move(api_permissions),
                              ManifestPermissionSet(), URLPatternSet(),
                              URLPatternSet());
    EXPECT_TRUE(VerifyOnePermissionMessage(
        permissions, Manifest::TYPE_EXTENSION,
        l10n_util::GetStringUTF16(
            IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ_ON_ALL_DEVICES)));
  }
  {
    APIPermissionSet api_permissions;
    api_permissions.insert(APIPermissionID::kHistory);
    api_permissions.insert(APIPermissionID::kTab);
    api_permissions.insert(APIPermissionID::kTopSites);
    api_permissions.insert(APIPermissionID::kProcesses);
    api_permissions.insert(APIPermissionID::kWebNavigation);
    api_permissions.insert(APIPermissionID::kSessions);
    PermissionSet permissions(std::move(api_permissions),
                              ManifestPermissionSet(), URLPatternSet(),
                              URLPatternSet());
    EXPECT_TRUE(VerifyOnePermissionMessage(
        permissions, Manifest::TYPE_EXTENSION,
        l10n_util::GetStringUTF16(
            IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE_ON_ALL_DEVICES)));
  }
}

#if BUILDFLAG(ENABLE_EXTENSIONS)
// TODO(crbug.com/441574787): Port this test to desktop Android. The error
// message is "Read and change all your data on all websites" instead of
// "Block parts of web pages". It's possible this is a manifest v2 versus
// manifest v3 issue.
TEST(PermissionsTest, GetWarningMessages_DeclarativeWebRequest) {
  // Test that if the declarativeWebRequest permission is present
  // in combination with all hosts permission, then only the warning
  // for host permissions is shown, because that covers the use of
  // declarativeWebRequest.

  // Until Declarative Web Request is in stable, let's make sure it is enabled
  // on the current channel.
  ScopedCurrentChannel sc(version_info::Channel::CANARY);

  {
    // First verify that declarativeWebRequest produces a message when host
    // permissions do not cover all hosts.
    scoped_refptr<const Extension> extension = LoadManifest(
        "permissions", "web_request_not_all_host_permissions.json");
    const PermissionSet& set =
        extension->permissions_data()->active_permissions();
    EXPECT_TRUE(VerifyHasPermissionMessage(set, extension->GetType(),
                                           "Block parts of web pages"));
    EXPECT_FALSE(VerifyHasPermissionMessage(
        set, extension->GetType(),
        "Read and change all your data on all websites"));
  }

  {
  // Now verify that declarativeWebRequest does not produce a message when host
  // permissions do cover all hosts.
  scoped_refptr<const Extension> extension =
      LoadManifest("permissions", "web_request_all_host_permissions.json");
  const PermissionSet& set =
      extension->permissions_data()->active_permissions();
  EXPECT_FALSE(VerifyHasPermissionMessage(set, extension->GetType(),
                                          "Block parts of web pages"));
  EXPECT_TRUE(VerifyHasPermissionMessage(
      set, extension->GetType(),
      "Read and change all your data on all websites"));
  }
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
// "serial" is a platform app API.
TEST(PermissionsTest, GetWarningMessages_Serial) {
  scoped_refptr<Extension> extension =
      LoadManifest("permissions", "serial.json");

  EXPECT_TRUE(extension->is_platform_app());
  EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
      APIPermissionID::kSerial));
  EXPECT_TRUE(VerifyOnePermissionMessage(extension->permissions_data(),
                                         "Access your serial devices"));
}

// "socket" is a platform app API.
TEST(PermissionsTest, GetWarningMessages_Socket_AnyHost) {
  ScopedCurrentChannel channel(version_info::Channel::DEV);

  scoped_refptr<Extension> extension =
      LoadManifest("permissions", "socket_any_host.json");
  EXPECT_TRUE(extension->is_platform_app());
  EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
      APIPermissionID::kSocket));
  EXPECT_TRUE(VerifyOnePermissionMessage(
      extension->permissions_data(),
      "Exchange data with any device on the local network or internet"));
}

// "socket" is a platform app API.
TEST(PermissionsTest, GetWarningMessages_Socket_OneDomainTwoHostnames) {
  ScopedCurrentChannel channel(version_info::Channel::DEV);

  scoped_refptr<Extension> extension =
      LoadManifest("permissions", "socket_one_domain_two_hostnames.json");
  EXPECT_TRUE(extension->is_platform_app());
  EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
      APIPermissionID::kSocket));

  // Verify the warnings, including support for unicode characters, the fact
  // that domain host warnings come before specific host warnings, and the fact
  // that domains and hostnames are in alphabetical order regardless of the
  // order in the manifest file.
  EXPECT_TRUE(VerifyTwoPermissionMessages(
      extension->permissions_data(),
      "Exchange data with any device in the domain example.org",
      "Exchange data with the devices named: "
      "b\xC3\xA5r.example.com foo.example.com",
      // "\xC3\xA5" = UTF-8 for lowercase A with ring above
      true));
}

// "socket" is a platform app API.
TEST(PermissionsTest, GetWarningMessages_Socket_TwoDomainsOneHostname) {
  ScopedCurrentChannel channel(version_info::Channel::DEV);

  scoped_refptr<Extension> extension =
      LoadManifest("permissions", "socket_two_domains_one_hostname.json");
  EXPECT_TRUE(extension->is_platform_app());
  EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
      APIPermissionID::kSocket));

  // Verify the warnings, including the fact that domain host warnings come
  // before specific host warnings and the fact that domains and hostnames are
  // in alphabetical order regardless of the order in the manifest file.
  EXPECT_TRUE(VerifyTwoPermissionMessages(
      extension->permissions_data(),
      "Exchange data with any device in the domains: "
      "example.com foo.example.org",
      "Exchange data with the device named bar.example.org", true));
}

// Since platform apps always use isolated storage, they can't (silently)
// access user data on other domains, so there's no need to prompt about host
// permissions. See crbug.com/255229.
TEST(PermissionsTest, GetWarningMessages_PlatformAppHosts) {
  scoped_refptr<Extension> extension =
      LoadManifest("permissions", "platform_app_hosts.json");
  EXPECT_TRUE(extension->is_platform_app());
  EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));

  extension = LoadManifest("permissions", "platform_app_all_urls.json");
  EXPECT_TRUE(extension->is_platform_app());
  EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));
}
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

testing::AssertionResult ShowsAllHostsWarning(const std::string& pattern) {
  scoped_refptr<const Extension> extension =
      ExtensionBuilder("TLDWildCardTest").AddHostPermission(pattern).Build();

  return VerifyHasPermissionMessage(
      extension->permissions_data(),
      l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS));
}

TEST(PermissionsTest, GetWarningMessages_TLDWildcardTreatedAsAllHosts) {
  EXPECT_TRUE(ShowsAllHostsWarning("http://*.com/*"));    // most popular.
  EXPECT_TRUE(ShowsAllHostsWarning("http://*.org/*"));    // sanity check.
  EXPECT_TRUE(ShowsAllHostsWarning("http://*.co.uk/*"));  // eTLD.
  EXPECT_TRUE(ShowsAllHostsWarning("http://*.de/*"));  // foreign country tld.

  // We should still show the normal permissions (i.e., "Can access your data on
  // *.rdcronin.com") for things that are not TLDs.
  EXPECT_FALSE(ShowsAllHostsWarning("http://*.rdcronin.com/*"));

  // Pseudo-TLDs, like appspot.com, should not show all hosts.
  EXPECT_FALSE(ShowsAllHostsWarning("http://*.appspot.com/*"));

  // Non-TLDs should be likewise exempt.
  EXPECT_FALSE(ShowsAllHostsWarning("http://*.notarealtld/*"));

  // Our internal checks use "foo", so let's make sure we're not messing
  // something up with it.
  EXPECT_FALSE(ShowsAllHostsWarning("http://*.foo.com"));
  EXPECT_FALSE(ShowsAllHostsWarning("http://foo.com"));
  // This will fail if foo becomes a recognized TLD. Which could be soon.
  // Update as needed.
  EXPECT_FALSE(ShowsAllHostsWarning("http://*.foo"));
}

TEST(PermissionsTest, GetDistinctHosts) {
  URLPatternSet explicit_hosts;
  std::set<std::string> expected;
  expected.insert("www.foo.com");
  expected.insert("www.bar.com");
  expected.insert("www.baz.com");

  {
    SCOPED_TRACE("no dupes");

    // Simple list with no dupes.
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path"));
    EXPECT_EQ(expected,
              permission_message_util::GetDistinctHosts(
                  explicit_hosts, true, true));
  }

  {
    SCOPED_TRACE("two dupes");

    // Add some dupes.
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path"));
    EXPECT_EQ(expected,
              permission_message_util::GetDistinctHosts(
                  explicit_hosts, true, true));
  }

  {
    SCOPED_TRACE("schemes differ");

    // Add a pattern that differs only by scheme. This should be filtered out.
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTPS, "https://www.bar.com/path"));
    EXPECT_EQ(expected,
              permission_message_util::GetDistinctHosts(
                  explicit_hosts, true, true));
  }

  {
    SCOPED_TRACE("paths differ");

    // Add some dupes by path.
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/pathypath"));
    EXPECT_EQ(expected,
              permission_message_util::GetDistinctHosts(
                  explicit_hosts, true, true));
  }

  {
    SCOPED_TRACE("subdomains differ");

    // We don't do anything special for subdomains.
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://monkey.www.bar.com/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://bar.com/path"));

    expected.insert("monkey.www.bar.com");
    expected.insert("bar.com");

    EXPECT_EQ(expected,
              permission_message_util::GetDistinctHosts(
                  explicit_hosts, true, true));
  }

  {
    SCOPED_TRACE("RCDs differ");

    // Now test for RCD uniquing.
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.de/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca.us/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com.my/path"));

    // This is an unknown RCD, which shouldn't be uniqued out.
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path"));
    // But it should only occur once.
    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path"));

    expected.insert("www.foo.xyzzy");

    EXPECT_EQ(expected,
              permission_message_util::GetDistinctHosts(
                  explicit_hosts, true, true));
  }

  {
    SCOPED_TRACE("wildcards");

    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*"));

    expected.insert("*.google.com");

    EXPECT_EQ(expected,
              permission_message_util::GetDistinctHosts(
                  explicit_hosts, true, true));
  }

  {
    SCOPED_TRACE("scriptable hosts");

    explicit_hosts.ClearPatterns();
    URLPatternSet scriptable_hosts;
    expected.clear();

    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*"));
    scriptable_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_HTTP, "http://*.example.com/*"));

    expected.insert("*.google.com");
    expected.insert("*.example.com");

    PermissionSet perm_set(APIPermissionSet(), ManifestPermissionSet(),
                           std::move(explicit_hosts),
                           std::move(scriptable_hosts));
    EXPECT_EQ(expected, permission_message_util::GetDistinctHosts(
                            perm_set.effective_hosts(), true, true));
  }

  {
    // We don't display warnings for file URLs because they are off by default.
    SCOPED_TRACE("file urls");

    explicit_hosts.ClearPatterns();
    expected.clear();

    explicit_hosts.AddPattern(
        URLPattern(URLPattern::SCHEME_FILE, "file:///*"));

    EXPECT_EQ(expected,
              permission_message_util::GetDistinctHosts(
                  explicit_hosts, true, true));
  }
}

TEST(PermissionsTest, GetDistinctHosts_ComIsBestRcd) {
  URLPatternSet explicit_hosts;
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));

  std::set<std::string> expected;
  expected.insert("www.foo.com");
  EXPECT_EQ(expected,
            permission_message_util::GetDistinctHosts(
                explicit_hosts, true, true));
}

TEST(PermissionsTest, GetDistinctHosts_NetIs2ndBestRcd) {
  URLPatternSet explicit_hosts;
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
  // No http://www.foo.com/path

  std::set<std::string> expected;
  expected.insert("www.foo.net");
  EXPECT_EQ(expected,
            permission_message_util::GetDistinctHosts(
                explicit_hosts, true, true));
}

TEST(PermissionsTest, GetDistinctHosts_OrgIs3rdBestRcd) {
  URLPatternSet explicit_hosts;
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
  // No http://www.foo.net/path
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
  // No http://www.foo.com/path

  std::set<std::string> expected;
  expected.insert("www.foo.org");
  EXPECT_EQ(expected,
            permission_message_util::GetDistinctHosts(
                explicit_hosts, true, true));
}

TEST(PermissionsTest, GetDistinctHosts_FirstInListIs4thBestRcd) {
  URLPatternSet explicit_hosts;
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
  // No http://www.foo.org/path
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
  // No http://www.foo.net/path
  explicit_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
  // No http://www.foo.com/path

  std::set<std::string> expected;
  expected.insert("www.foo.ca");
  EXPECT_EQ(expected,
            permission_message_util::GetDistinctHosts(
                explicit_hosts, true, true));
}

TEST(PermissionsTest, IsHostPrivilegeIncrease) {
  struct TestCases {
    struct host_spec {
      int schemes;
      std::string pattern;
    };
    std::vector<host_spec> initial_hosts;
    std::vector<host_spec> final_hosts;
    Manifest::Type type;
    bool is_increase;
    bool reverse_is_increase;
  };
  const auto test_cases = std::to_array<TestCases>({
      // Order doesn't matter.
      {{{URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"},
        {URLPattern::SCHEME_HTTP, "http://www.google.com/path"}},
       {{URLPattern::SCHEME_HTTP, "http://www.google.com/path"},
        {URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"}},
       Manifest::TYPE_EXTENSION,
       false,
       false},
      // Paths are ignored.
      {{{URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"},
        {URLPattern::SCHEME_HTTP, "http://www.google.com/path"}},
       {{URLPattern::SCHEME_HTTP, "http://www.google.com/*"}},
       Manifest::TYPE_EXTENSION,
       false,
       false},
      // RCDs are ignored.
      {{{URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"},
        {URLPattern::SCHEME_HTTP, "http://www.google.com/path"}},
       {{URLPattern::SCHEME_HTTP, "http://www.google.com.hk/*"}},
       Manifest::TYPE_EXTENSION,
       false,
       false},
      // Subdomain wildcards are handled properly.
      {{{URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"},
        {URLPattern::SCHEME_HTTP, "http://www.google.com/path"}},
       {{URLPattern::SCHEME_HTTP, "http://*.google.com.hk/*"}},
       Manifest::TYPE_EXTENSION,
       true,
       false},
      // Different domains count as different hosts.
      {{{URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"},
        {URLPattern::SCHEME_HTTP, "http://www.google.com/path"}},
       {{URLPattern::SCHEME_HTTP, "http://www.google.com/path"},
        {URLPattern::SCHEME_HTTP, "http://www.example.org/path"}},
       Manifest::TYPE_EXTENSION,
       true,
       false},
      // Different subdomains count as different hosts.
      {{{URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"},
        {URLPattern::SCHEME_HTTP, "http://www.google.com/path"}},
       {{URLPattern::SCHEME_HTTP, "http://mail.google.com/*"}},
       Manifest::TYPE_EXTENSION,
       true,
       true},
      // Moving from all subdomains to the domain should not be
      // an increase in permissions. However, moving from just
      // the domain to all of the subdomains should be.
      {{{URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS,
         "*://*.google.com/*"}},
       {{URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS,
         "*://google.com/*"}},
       Manifest::TYPE_EXTENSION,
       false,
       true},
      // Platform apps should not have host permissions increases.
      {{{URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"},
        {URLPattern::SCHEME_HTTP, "http://www.google.com/path"}},
       {{URLPattern::SCHEME_HTTP, "http://mail.google.com/*"}},
       Manifest::TYPE_PLATFORM_APP,
       false,
       false},
      // Test that subdomain wildcard matching from crbug.com://65337
      // works.
      {{{URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS,
         "*://*.google.com/"},
        {URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS,
         "*://mail.google.com/"}},
       {{URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS,
         "*://inbox.google.com/"}},
       Manifest::TYPE_EXTENSION,
       false,
       true},
      // Test the "all_urls" meta-pattern.
      {{{URLPattern::SCHEME_ALL, "<all_urls>"}},
       {{URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS,
         "*://inbox.google.com/"}},
       Manifest::TYPE_EXTENSION,
       false,
       true},
      // Test expanding from any .com host to any host in any TLD.
      // TODO(crbug.com/40579475): Should this really be a permissions increase?
      {{{URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS, "*://*.com/*"}},
       {{URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS, "*://*/*"}},
       Manifest::TYPE_EXTENSION,
       true,
       false},
  });
  const PermissionMessageProvider* provider = PermissionMessageProvider::Get();
  for (size_t i = 0; i < std::size(test_cases); ++i) {
    URLPatternSet explicit_hosts1;
    URLPatternSet explicit_hosts2;
    const auto& test_case = test_cases[i];
    for (const auto& initial_host : test_case.initial_hosts) {
      explicit_hosts1.AddPattern(
          URLPattern(initial_host.schemes, initial_host.pattern));
    }
    for (const auto& final_host : test_case.final_hosts) {
      explicit_hosts2.AddPattern(
          URLPattern(final_host.schemes, final_host.pattern));
    }
    const PermissionSet set1(APIPermissionSet(), ManifestPermissionSet(),
                             std::move(explicit_hosts1), URLPatternSet());
    const PermissionSet set2(APIPermissionSet(), ManifestPermissionSet(),
                             std::move(explicit_hosts2), URLPatternSet());
    EXPECT_EQ(test_case.is_increase,
              provider->IsPrivilegeIncrease(set1, set2, test_case.type))
        << "Failure at index " << i;
    EXPECT_EQ(test_case.reverse_is_increase,
              provider->IsPrivilegeIncrease(set2, set1, test_case.type))
        << "Failure at index " << i;
  }
}

TEST(PermissionsTest, GetAPIsAsStrings) {
  APIPermissionSet apis;

  apis.insert(APIPermissionID::kProxy);
  apis.insert(APIPermissionID::kBackground);
  apis.insert(APIPermissionID::kNotifications);
  apis.insert(APIPermissionID::kTab);

  PermissionSet perm_set(apis.Clone(), ManifestPermissionSet(), URLPatternSet(),
                         URLPatternSet());
  std::set<std::string> api_names = perm_set.GetAPIsAsStrings();

  // The result is correct if it has the same number of elements
  // and we can convert it back to the id set.
  EXPECT_EQ(4u, api_names.size());
  EXPECT_EQ(apis,
            PermissionsInfo::GetInstance()->GetAllByNameForTest(api_names));
}

TEST(PermissionsTest, IsEmpty) {
  std::unique_ptr<const PermissionSet> empty(new PermissionSet());
  EXPECT_TRUE(empty->IsEmpty());
  std::unique_ptr<const PermissionSet> perm_set;

  perm_set = std::make_unique<PermissionSet>(APIPermissionSet(),
                                             ManifestPermissionSet(),
                                             URLPatternSet(), URLPatternSet());
  EXPECT_TRUE(perm_set->IsEmpty());

  APIPermissionSet non_empty_apis;
  non_empty_apis.insert(APIPermissionID::kBackground);
  perm_set = std::make_unique<PermissionSet>(std::move(non_empty_apis),
                                             ManifestPermissionSet(),
                                             URLPatternSet(), URLPatternSet());
  EXPECT_FALSE(perm_set->IsEmpty());

  // Try non standard host
  URLPatternSet non_empty_extent;
  AddPattern(&non_empty_extent, "http://www.google.com/*");

  perm_set = std::make_unique<PermissionSet>(
      APIPermissionSet(), ManifestPermissionSet(), non_empty_extent.Clone(),
      URLPatternSet());
  EXPECT_FALSE(perm_set->IsEmpty());

  perm_set = std::make_unique<PermissionSet>(
      APIPermissionSet(), ManifestPermissionSet(), URLPatternSet(),
      non_empty_extent.Clone());
  EXPECT_FALSE(perm_set->IsEmpty());
}

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
// "syncFileSystem" is a platform app permission.
TEST(PermissionsTest, SyncFileSystemPermission) {
  scoped_refptr<Extension> extension = LoadManifest(
      "permissions", "sync_file_system.json");
  APIPermissionSet apis;
  apis.insert(APIPermissionID::kSyncFileSystem);
  EXPECT_TRUE(extension->is_platform_app());
  EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
      APIPermissionID::kSyncFileSystem));
  EXPECT_TRUE(
      VerifyOnePermissionMessage(extension->permissions_data(),
                                 "Store data in your Google Drive account"));
}
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

// Make sure that we don't crash when we're trying to show the permissions
// even though everything with a chrome:// scheme except chrome://favicon is
// not a valid permission.
// More details here: crbug/246314.
TEST(PermissionsTest, ChromeURLs) {
  URLPatternSet allowed_hosts;
  allowed_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_ALL, "http://www.google.com/"));
  allowed_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_ALL, "chrome://favicon/"));
  allowed_hosts.AddPattern(
      URLPattern(URLPattern::SCHEME_ALL, "chrome://not-favicon/"));
  PermissionSet permissions(APIPermissionSet(), ManifestPermissionSet(),
                            std::move(allowed_hosts), URLPatternSet());
  PermissionMessageProvider::Get()->GetPermissionMessages(
      PermissionMessageProvider::Get()->GetAllPermissionIDs(
          permissions, Manifest::TYPE_EXTENSION));
}

TEST(PermissionsTest, IsPrivilegeIncrease_DeclarativeWebRequest) {
  scoped_refptr<Extension> extension(
      LoadManifest("permissions", "permissions_all_urls.json"));
  const PermissionSet& permissions =
      extension->permissions_data()->active_permissions();

  scoped_refptr<Extension> extension_dwr(
      LoadManifest("permissions", "web_request_all_host_permissions.json"));
  const PermissionSet& permissions_dwr =
      extension_dwr->permissions_data()->active_permissions();

  EXPECT_FALSE(PermissionMessageProvider::Get()->IsPrivilegeIncrease(
      permissions, permissions_dwr, extension->GetType()));
}

// Exercises setting different members in the PermissionSet. Due to varying
// amounts of initialization, these can be non-trivial and have side-effects.
TEST(PermissionsTest, SettingMembers) {
  URLPattern first_host(URLPattern::SCHEME_ALL, "http://first.example/*");
  URLPattern second_host(URLPattern::SCHEME_ALL, "http://second.example/*");
  URLPattern third_host(URLPattern::SCHEME_ALL, "http://third.example/*");
  URLPattern all_hosts(URLPattern::SCHEME_ALL, "<all_urls>");

  {
    // Setting explicit hosts also sets effective hosts.
    PermissionSet set(APIPermissionSet(), ManifestPermissionSet(),
                      URLPatternSet({first_host}),
                      URLPatternSet({second_host}));
    set.SetExplicitHosts(URLPatternSet({third_host}));
    EXPECT_EQ(URLPatternSet({third_host}), set.explicit_hosts());
    EXPECT_EQ(URLPatternSet({second_host}), set.scriptable_hosts());
    EXPECT_EQ(URLPatternSet({second_host, third_host}), set.effective_hosts());
  }

  {
    // Setting scriptable hosts also sets effective hosts.
    PermissionSet set(APIPermissionSet(), ManifestPermissionSet(),
                      URLPatternSet({first_host}),
                      URLPatternSet({second_host}));
    set.SetScriptableHosts(URLPatternSet({third_host}));
    EXPECT_EQ(URLPatternSet({first_host}), set.explicit_hosts());
    EXPECT_EQ(URLPatternSet({third_host}), set.scriptable_hosts());
    EXPECT_EQ(URLPatternSet({first_host, third_host}), set.effective_hosts());
  }

  {
    // Setting explicit hosts recalculates whether to warn for all URLs.
    PermissionSet set(APIPermissionSet(), ManifestPermissionSet(),
                      URLPatternSet({first_host}),
                      URLPatternSet({second_host}));
    EXPECT_FALSE(set.ShouldWarnAllHosts());
    set.SetExplicitHosts(URLPatternSet({all_hosts}));
    EXPECT_TRUE(set.ShouldWarnAllHosts());
  }

  {
    // Setting scriptable hosts recalculates whether to warn for all URLs.
    PermissionSet set(APIPermissionSet(), ManifestPermissionSet(),
                      URLPatternSet({first_host}),
                      URLPatternSet({second_host}));
    EXPECT_FALSE(set.ShouldWarnAllHosts());
    set.SetExplicitHosts(URLPatternSet({all_hosts}));
    EXPECT_TRUE(set.ShouldWarnAllHosts());
  }

  {
    // Newly-set explicit hosts have their paths set to "/*".
    PermissionSet set(APIPermissionSet(), ManifestPermissionSet(),
                      URLPatternSet({first_host}),
                      URLPatternSet({second_host}));
    URLPattern custom_path(URLPattern::SCHEME_ALL, "https://path.example/path");
    URLPattern cleaned_path(URLPattern::SCHEME_ALL, "https://path.example/*");
    set.SetExplicitHosts(URLPatternSet({custom_path}));
    EXPECT_EQ(URLPatternSet({cleaned_path}), set.explicit_hosts());
  }

  {
    // Setting API permissions recalculates whether to warn for all URLs.
    APIPermissionSet apis;
    apis.insert(APIPermissionID::kTab);
    PermissionSet set(std::move(apis), ManifestPermissionSet(), URLPatternSet(),
                      URLPatternSet());
    EXPECT_FALSE(set.ShouldWarnAllHosts());
    APIPermissionSet new_apis;
    new_apis.insert(APIPermissionID::kDebugger);
    set.SetAPIPermissions(std::move(new_apis));
    EXPECT_TRUE(set.ShouldWarnAllHosts());
  }

  {
    // Setting API permissions adds implicit permissions.
    PermissionSet set;
    APIPermissionSet new_apis;
    new_apis.insert(APIPermissionID::kFileBrowserHandler);
    set.SetAPIPermissions(std::move(new_apis));
    EXPECT_TRUE(set.HasAPIPermission(APIPermissionID::kFileBrowserHandler));
  }
}

}  // namespace extensions