// Copyright 2014 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/browser/webstore_installer.h"

#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/webstore_installer_test.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/install_approval.h"
#include "extensions/browser/permissions/scripting_permissions_modifier.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

namespace {

const char kWebstoreDomain[] = "cws.com";
const char kAppDomain[] = "app.com";
const char kNonAppDomain[] = "nonapp.com";
const char kTestExtensionId[] = "ecglahbcnmdpdciemllbhojghbkagdje";
const char kTestExtensionWithPermissionsId[] =
    "lpbboafeefjeccjhdhcfdibnjcecpmhd";
const char kTestDataPath[] = "extensions/api_test/webstore_inline_install";
const char kCrxFilename[] = "extension.crx";
const char kCrxWithPermissionsFilename[] =
    "extension_with_host_permissions.crx";

}  // namespace

// Test version of WebstoreInstaller that intercepts the destructor.
class TestWebstoreInstaller : public WebstoreInstaller {
 public:
  TestWebstoreInstaller(Profile* profile,
                        SuccessCallback success_callback,
                        FailureCallback failure_callback,
                        content::WebContents* web_contents,
                        const std::string& id,
                        std::unique_ptr<InstallApproval> approval,
                        InstallSource source)
      : WebstoreInstaller(profile,
                          std::move(success_callback),
                          std::move(failure_callback),
                          web_contents,
                          id,
                          std::move(approval),
                          source) {}

  void SetDeletedClosure(base::OnceClosure cb) {
    deleted_closure_ = std::move(cb);
  }

 private:
  ~TestWebstoreInstaller() override {
    if (!deleted_closure_.is_null())
      std::move(deleted_closure_).Run();
  }

  base::OnceClosure deleted_closure_;
};

class WebstoreInstallerBrowserTest : public WebstoreInstallerTest {
 public:
  WebstoreInstallerBrowserTest(const std::string& webstore_domain,
                               const std::string& test_data_path,
                               const std::string& crx_filename,
                               const std::string& verified_domain,
                               const std::string& unverified_domain)
      : WebstoreInstallerTest(webstore_domain,
                              test_data_path,
                              crx_filename,
                              verified_domain,
                              unverified_domain) {}
  ~WebstoreInstallerBrowserTest() override = default;

  void SetDoneClosure(base::OnceClosure done_closure) {
    done_closure_ = std::move(done_closure);
  }

  bool success() const { return success_; }

  void OnExtensionInstallSuccess(const std::string& id) {
    success_ = true;
    std::move(done_closure_).Run();
  }

  void OnExtensionInstallFailure(const std::string& id,
                                 const std::string& error,
                                 WebstoreInstaller::FailureReason reason) {
    success_ = false;
    std::move(done_closure_).Run();
  }

 private:
  base::OnceClosure done_closure_;
  bool success_;
};

class WebstoreInstallerMV2BrowserTest : public WebstoreInstallerBrowserTest {
 public:
  WebstoreInstallerMV2BrowserTest()
      : WebstoreInstallerBrowserTest(kWebstoreDomain,
                                     kTestDataPath,
                                     kCrxFilename,
                                     kAppDomain,
                                     kNonAppDomain) {}
  ~WebstoreInstallerMV2BrowserTest() override = default;

  // The manifest used by the test installer must match `kCrxFilename` manifest
  // in the test directory.
  base::DictValue GetManifest() {
    return base::DictValue()
        .Set("name", "Installer Extension")
        .Set("manifest_version", 2)
        .Set("version", "1.0")
        .Set("permissions", base::ListValue().Append("tabs"));
  }
};

IN_PROC_BROWSER_TEST_F(WebstoreInstallerMV2BrowserTest, WebstoreInstall) {
  content::WebContents* active_web_contents = GetActiveWebContents();
  ASSERT_TRUE(active_web_contents);

  // Create an approval.
  std::unique_ptr<InstallApproval> approval =
      InstallApproval::CreateWithNoInstallPrompt(profile(), kTestExtensionId,
                                                 GetManifest(), false);

  // Create and run a WebstoreInstaller.
  base::RunLoop run_loop;
  SetDoneClosure(run_loop.QuitClosure());
  TestWebstoreInstaller* installer = new TestWebstoreInstaller(
      profile(),
      base::BindOnce(&WebstoreInstallerBrowserTest::OnExtensionInstallSuccess,
                     base::Unretained(this)),
      base::BindOnce(&WebstoreInstallerBrowserTest::OnExtensionInstallFailure,
                     base::Unretained(this)),
      active_web_contents, kTestExtensionId, std::move(approval),
      WebstoreInstaller::INSTALL_SOURCE_OTHER);
  installer->Start();
  run_loop.Run();

  EXPECT_TRUE(success());
  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
  ASSERT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
}

IN_PROC_BROWSER_TEST_F(WebstoreInstallerMV2BrowserTest, SimultaneousInstall) {
  base::DictValue manifest = GetManifest();

  content::WebContents* active_web_contents = GetActiveWebContents();
  ASSERT_TRUE(active_web_contents);

  // Create an approval.
  std::unique_ptr<InstallApproval> approval =
      InstallApproval::CreateWithNoInstallPrompt(profile(), kTestExtensionId,
                                                 manifest.Clone(), false);

  // Create and run a WebstoreInstaller.
  base::RunLoop run_loop;
  SetDoneClosure(run_loop.QuitClosure());
  scoped_refptr<TestWebstoreInstaller> installer = new TestWebstoreInstaller(
      profile(),
      base::BindOnce(&WebstoreInstallerBrowserTest::OnExtensionInstallSuccess,
                     base::Unretained(this)),
      base::BindOnce(&WebstoreInstallerBrowserTest::OnExtensionInstallFailure,
                     base::Unretained(this)),
      active_web_contents, kTestExtensionId, std::move(approval),
      WebstoreInstaller::INSTALL_SOURCE_OTHER);
  installer->Start();

  // Simulate another mechanism installing the same extension.
  scoped_refptr<const Extension> extension =
      ExtensionBuilder()
          .SetLocation(mojom::ManifestLocation::kInternal)
          .SetID(kTestExtensionId)
          .SetManifest(std::move(manifest))
          .Build();
  ExtensionRegistrar::Get(profile())->OnExtensionInstalled(
      extension.get(), syncer::StringOrdinal(), 0);

  run_loop.Run();

  // Wait for the WebstoreInstaller to be destroyed. Bad things happen if we
  // don't wait for this.
  base::RunLoop run_loop2;
  installer->SetDeletedClosure(run_loop2.QuitClosure());
  installer = nullptr;
  run_loop2.Run();

  EXPECT_TRUE(success());
  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
  // Extension ends up as disabled because of permissions.
  ASSERT_TRUE(registry->disabled_extensions().GetByID(kTestExtensionId));
}

class WebstoreInstallerWithWithholdingUIBrowserTest
    : public WebstoreInstallerBrowserTest,
      public testing::WithParamInterface<bool> {
 public:
  WebstoreInstallerWithWithholdingUIBrowserTest()
      : WebstoreInstallerBrowserTest(kWebstoreDomain,
                                     kTestDataPath,
                                     kCrxWithPermissionsFilename,
                                     kAppDomain,
                                     kNonAppDomain) {
    feature_list_.InitAndEnableFeature(
        extensions_features::kAllowWithholdingExtensionPermissionsOnInstall);
  }
  ~WebstoreInstallerWithWithholdingUIBrowserTest() override = default;

  // Th manifest used by the test installer must match
  // `kCrxWithPermissionsFilename` manifest in the test directory.
  base::DictValue GetManifest() {
    return base::DictValue()
        .Set("name", "Installer Extension")
        .Set("manifest_version", 3)
        .Set("version", "1.0")
        .Set("host_permissions", base::ListValue().Append("<all_urls>"));
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Tests host permissions are withheld at installation only when the checkbox is
// selected.
IN_PROC_BROWSER_TEST_P(WebstoreInstallerWithWithholdingUIBrowserTest,
                       WithholdingHostsOnInstall) {
  bool shoud_withhold_permissions = GetParam();

  content::WebContents* active_web_contents = GetActiveWebContents();
  ASSERT_TRUE(active_web_contents);

  // Create an approval that withholds permissions when the checkbox is not
  // selected.
  std::unique_ptr<InstallApproval> approval =
      InstallApproval::CreateWithNoInstallPrompt(
          profile(), kTestExtensionWithPermissionsId, GetManifest(), false);
  approval->withhold_permissions = shoud_withhold_permissions;

  // Create and run a WebstoreInstaller.
  base::RunLoop run_loop;
  SetDoneClosure(run_loop.QuitClosure());
  TestWebstoreInstaller* installer = new TestWebstoreInstaller(
      profile(),
      base::BindOnce(&WebstoreInstallerBrowserTest::OnExtensionInstallSuccess,
                     base::Unretained(this)),
      base::BindOnce(&WebstoreInstallerBrowserTest::OnExtensionInstallFailure,
                     base::Unretained(this)),
      active_web_contents, kTestExtensionWithPermissionsId, std::move(approval),
      WebstoreInstaller::INSTALL_SOURCE_OTHER);
  installer->Start();
  run_loop.Run();

  // Verify extension was installed.
  EXPECT_TRUE(success());
  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
  const Extension* extension =
      registry->enabled_extensions().GetByID(kTestExtensionWithPermissionsId);
  ASSERT_TRUE(extension);

  // Host permissions should be withheld only when the params indicate so.
  PermissionsManager* permissions_manager = PermissionsManager::Get(profile());
  EXPECT_EQ(permissions_manager->HasWithheldHostPermissions(*extension),
            shoud_withhold_permissions);

  // Access to google.com should be withheld only when the params indicate so.
  const PermissionsManager::ExtensionSiteAccess site_access =
      permissions_manager->GetSiteAccess(*extension,
                                         GURL("https://www.google.com"));
  EXPECT_EQ(site_access.withheld_site_access, shoud_withhold_permissions);
  EXPECT_EQ(site_access.has_site_access, !shoud_withhold_permissions);
}

INSTANTIATE_TEST_SUITE_P(All,
                         WebstoreInstallerWithWithholdingUIBrowserTest,
                         testing::Bool());

}  // namespace extensions
