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

#include "chrome/browser/ash/extensions/extensions_permissions_tracker.h"

#include <iterator>

#include "ash/constants/ash_pref_names.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/browser_process.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/login/login_state/scoped_test_public_session_login_state.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/browser/pref_names.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/permissions/permissions_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::Return;

namespace extensions {

namespace {
constexpr char kExtensionId1[] = "id1";
constexpr char kExtensionId2[] = "id2";
constexpr char kExtensionId3[] = "id3";
constexpr char kExtensionUrl1[] = "url1";
constexpr char kExtensionUrl2[] = "url2";
constexpr char kExtensionUrl3[] = "url3";
const char* const kSafePermissionsSet1[] = {"accessibilityFeatures.modify",
                                            "accessibilityFeatures.read"};

const char* const kSafePermissionsSet2[] = {"background", "alarms"};

const char* const kUnsafePermissionsSet1[] = {"debugger", "history", "input"};

const char* const kUnsafePermissionsSet2[] = {"topSites", "ttsEngine",
                                              "webNavigation"};
}  // namespace

class MockExtensionsPermissionsTracker : public ExtensionsPermissionsTracker {
 public:
  MockExtensionsPermissionsTracker(ExtensionRegistry* registry,
                                   content::BrowserContext* browser_context)
      : ExtensionsPermissionsTracker(
            TestingBrowserProcess::GetGlobal()->local_state(),
            registry,
            browser_context) {
    safe_permissions_.insert(std::begin(kSafePermissionsSet1),
                             std::end(kSafePermissionsSet1));
    safe_permissions_.insert(std::begin(kSafePermissionsSet2),
                             std::end(kSafePermissionsSet2));
  }

  // ExtensionsPermissionsTracker:
  bool IsSafePerms(const PermissionsData* perms_data) const override {
    std::set<std::string> perms_strings =
        perms_data->active_permissions().GetAPIsAsStrings();
    for (const auto& perm : perms_strings) {
      if (safe_permissions_.find(perm) == safe_permissions_.end())
        return false;
    }
    return true;
  }

 private:
  std::set<std::string> safe_permissions_;
};

class ExtensionsPermissionsTrackerTest : public testing::Test {
 public:
  ExtensionsPermissionsTrackerTest()
      : prefs_(profile_.GetTestingPrefService()),
        registry_(ExtensionRegistry::Get(&profile_)) {}

  ExtensionsPermissionsTrackerTest(const ExtensionsPermissionsTrackerTest&) =
      delete;
  ExtensionsPermissionsTrackerTest& operator=(
      const ExtensionsPermissionsTrackerTest&) = delete;

  base::DictValue SetupForceList() {
    base::DictValue dict;
    dict.Set(kExtensionId1, kExtensionUrl1);
    dict.Set(kExtensionId2, kExtensionUrl2);
    prefs_->SetManagedPref(pref_names::kInstallForceList, dict.Clone());
    return dict;
  }

  void SetupEmptyForceList() {
    prefs_->SetManagedPref(pref_names::kInstallForceList, base::DictValue());
  }

  void CreateExtensionsPermissionsTracker() {
    permissions_tracker_ = std::make_unique<MockExtensionsPermissionsTracker>(
        registry_, &profile_);
  }

  void AddExtensionWithIdAndPermissions(
      const std::string& extension_id,
      const std::vector<std::string>& permissions) {
    auto extension = ExtensionBuilder(extension_id)
                         .SetID(extension_id)
                         .AddAPIPermissions(permissions)
                         .Build();
    registry_->AddEnabled(extension);

    registry_->TriggerOnLoaded(extension.get());
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;
  TestingProfile profile_;
  raw_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_;
  raw_ptr<ExtensionRegistry> registry_;
  std::unique_ptr<MockExtensionsPermissionsTracker> permissions_tracker_;
};

TEST_F(ExtensionsPermissionsTrackerTest, EmptyForceList) {
  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));

  SetupEmptyForceList();
  CreateExtensionsPermissionsTracker();

  EXPECT_FALSE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, SafeForceListInstalled) {
  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));

  SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kSafePermissionsSet1),
                              std::end(kSafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId1, v1);

  std::vector<std::string> v2(std::begin(kSafePermissionsSet2),
                              std::end(kSafePermissionsSet2));
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  EXPECT_FALSE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, UnsafeForceListInstalled) {
  SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kUnsafePermissionsSet1),
                              std::end(kUnsafePermissionsSet1));

  std::vector<std::string> v2(std::begin(kUnsafePermissionsSet2),
                              std::end(kUnsafePermissionsSet2));

  AddExtensionWithIdAndPermissions(kExtensionId1, v1);
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, MixedForceListInstalled) {
  SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kUnsafePermissionsSet1),
                              std::end(kUnsafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId1, v1);

  std::vector<std::string> v2(std::begin(kSafePermissionsSet2),
                              std::end(kSafePermissionsSet2));
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, ForceListIncreased) {
  auto dict = SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kSafePermissionsSet1),
                              std::end(kSafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId1, v1);

  std::vector<std::string> v2(std::begin(kSafePermissionsSet2),
                              std::end(kSafePermissionsSet2));
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  EXPECT_FALSE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));

  dict.Set(kExtensionId3, kExtensionUrl3);
  prefs_->SetManagedPref(pref_names::kInstallForceList, std::move(dict));

  std::vector<std::string> v3(std::begin(kUnsafePermissionsSet1),
                              std::end(kUnsafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId3, v3);

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, ForceListDecreased) {
  auto dict = SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kUnsafePermissionsSet1),
                              std::end(kUnsafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId1, v1);

  std::vector<std::string> v2(std::begin(kSafePermissionsSet2),
                              std::end(kSafePermissionsSet2));
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));

  dict.Remove(kExtensionId1);
  prefs_->SetManagedPref(pref_names::kInstallForceList, std::move(dict));
  EXPECT_FALSE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, SafePendingExtensions) {
  auto dict = SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kSafePermissionsSet1),
                              std::end(kSafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId1, v1);

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));

  std::vector<std::string> v2(std::begin(kSafePermissionsSet2),
                              std::end(kSafePermissionsSet2));
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  EXPECT_FALSE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, UnsafePendingExtensions) {
  SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kSafePermissionsSet1),
                              std::end(kSafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId1, v1);

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));

  std::vector<std::string> v2(std::begin(kUnsafePermissionsSet1),
                              std::end(kUnsafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, UnsafeForceListChanged) {
  auto dict = SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kSafePermissionsSet1),
                              std::end(kSafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId1, v1);

  std::vector<std::string> v2(std::begin(kUnsafePermissionsSet1),
                              std::end(kUnsafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));

  dict.Remove(kExtensionId1);
  prefs_->SetManagedPref(pref_names::kInstallForceList, dict.Clone());

  EXPECT_TRUE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));

  dict.Remove(kExtensionId2);
  prefs_->SetManagedPref(pref_names::kInstallForceList, dict.Clone());

  EXPECT_FALSE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

TEST_F(ExtensionsPermissionsTrackerTest, OtherExtensionsLoaded) {
  SetupForceList();
  CreateExtensionsPermissionsTracker();

  std::vector<std::string> v1(std::begin(kSafePermissionsSet1),
                              std::end(kSafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId1, v1);

  std::vector<std::string> v2(std::begin(kSafePermissionsSet1),
                              std::end(kSafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId2, v2);

  std::vector<std::string> v3(std::begin(kUnsafePermissionsSet1),
                              std::end(kUnsafePermissionsSet1));
  AddExtensionWithIdAndPermissions(kExtensionId3, v3);

  EXPECT_FALSE(TestingBrowserProcess::GetGlobal()->local_state()->GetBoolean(
      ash::prefs::kManagedSessionUseFullLoginWarning));
}

}  // namespace extensions
