blob: 5c80ee80266f37097c500211600e4f385136714d [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string_view>
#include "base/auto_reset.h"
#include "base/check_deref.h"
#include "base/containers/map_util.h"
#include "base/files/file_util.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h"
#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h"
#include "chrome/browser/extensions/api/document_scan/document_scan_api_handler.h"
#include "chrome/browser/extensions/api/document_scan/document_scan_test_utils.h"
#include "chrome/browser/extensions/api/document_scan/fake_document_scan_ash.h"
#include "chrome/browser/extensions/api/document_scan/scanner_discovery_runner.h"
#include "chrome/browser/extensions/api/document_scan/start_scan_runner.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chromeos/crosapi/mojom/document_scan.mojom.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/common/constants.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/test/test_extension_dir.h"
namespace extensions {
namespace {
constexpr size_t kRealBackendMinimumReadSize = 32768;
// Enum used to initialize the parameterized test with different types of
// extensions.
enum class ExtensionType {
kChromeApp,
kExtensionMV2,
kExtensionMV3,
};
// Mapping of the different extension types used in the test to the specific
// manifest file names to create an extension of that type. The actual location
// of these files is at //chrome/test/data/extensions/api_test/document_scan/.
static constexpr auto kManifestFileNames =
base::MakeFixedFlatMap<ExtensionType, std::string_view>(
{{ExtensionType::kChromeApp, "manifest_chrome_app.json"},
{ExtensionType::kExtensionMV2, "manifest_extension_v2.json"},
{ExtensionType::kExtensionMV3, "manifest_extension_v3.json"}});
std::unique_ptr<TestExtensionDir> CreateDocumentScanExtension(
ExtensionType type) {
auto extension_dir = std::make_unique<TestExtensionDir>();
base::FilePath test_data_dir;
CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
base::FilePath document_scan_dir = test_data_dir.AppendASCII("extensions")
.AppendASCII("api_test")
.AppendASCII("document_scan");
base::ScopedAllowBlockingForTesting allow_blocking;
base::CopyDirectory(document_scan_dir, extension_dir->UnpackedPath(),
/*recursive=*/false);
extension_dir->CopyFileTo(document_scan_dir.AppendASCII(CHECK_DEREF(
base::FindOrNull(kManifestFileNames, type))),
extensions::kManifestFilename);
return extension_dir;
}
// Helper class that automatically adds extension IDs using the documentScan
// permission to the trusted extension list. This allows tests to set up a
// trusted extension even with the autogenerated extension ID that comes from
// RunExtensionTest.
class AutoTruster : public extensions::ExtensionRegistryObserver {
public:
explicit AutoTruster(ExtensionRegistry* registry) {
extension_registry_observation_.Observe(registry);
}
~AutoTruster() override = default;
void OnExtensionWillBeInstalled(content::BrowserContext* browser_context,
const Extension* extension,
bool is_update,
const std::string& old_name) override {
if (extension->permissions_data()->HasAPIPermission("documentScan")) {
PrefService* prefs =
Profile::FromBrowserContext(browser_context)->GetPrefs();
ScopedListPrefUpdate update(prefs,
prefs::kDocumentScanAPITrustedExtensions);
update->Append(extension->id());
}
}
private:
base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
extension_registry_observation_{this};
std::unique_ptr<ScopedListPrefUpdate> scoped_pref_update_;
};
} // namespace
class DocumentScanApiTest : public ExtensionApiTest,
public testing::WithParamInterface<ExtensionType> {
public:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
DocumentScanAPIHandler::Get(profile())->SetDocumentScanForTesting(
&document_scan_ash_);
document_scan()->SetSmallestMaxReadSize(kRealBackendMinimumReadSize);
}
void SetUpBrowserContextKeyedServices(
content::BrowserContext* context) override {
ExtensionApiTest::SetUpBrowserContextKeyedServices(context);
ash::LorgnetteScannerManagerFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return std::make_unique<ash::FakeLorgnetteScannerManager>();
}));
}
void SetScannerInfoList(std::vector<lorgnette::ScannerInfo> scanners) {
auto* scanner_manager = static_cast<ash::FakeLorgnetteScannerManager*>(
ash::LorgnetteScannerManagerFactory::GetForBrowserContext(profile()));
lorgnette::ListScannersResponse response;
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
for (auto& scanner : scanners) {
auto open_response = crosapi::mojom::OpenScannerResponse::New();
open_response->result = crosapi::mojom::ScannerOperationResult::kSuccess;
open_response->scanner_id = scanner.name();
open_response->scanner_handle = scanner.name() + "-handle";
open_response->options.emplace();
open_response->options.value()["option1"] =
CreateTestScannerOption("option1", 5);
document_scan()->SetOpenScannerResponse(scanner.name(),
std::move(open_response));
response.mutable_scanners()->Add(std::move(scanner));
}
scanner_manager->SetGetScannerInfoListResponse(response);
}
protected:
ExtensionType GetExtensionType() const { return GetParam(); }
void RunTest(const char* html_test_page) {
auto dir = CreateDocumentScanExtension(GetExtensionType());
auto run_options = GetExtensionType() == ExtensionType::kChromeApp
? RunOptions{.custom_arg = html_test_page,
.launch_as_platform_app = true}
: RunOptions({.extension_url = html_test_page});
ASSERT_TRUE(RunExtensionTest(dir->UnpackedPath(), run_options, {}));
}
FakeDocumentScanAsh* document_scan() { return &document_scan_ash_; }
private:
FakeDocumentScanAsh document_scan_ash_;
};
IN_PROC_BROWSER_TEST_P(DocumentScanApiTest, TestLoadPermissions) {
// This test simply checks to see if we have the correct permissions to load
// the extension.
RunTest("load_permissions.html");
}
IN_PROC_BROWSER_TEST_P(DocumentScanApiTest, GetScannerList_DiscoveryDenied) {
ScannerDiscoveryRunner::SetDiscoveryConfirmationResultForTesting(false);
RunTest("get_scanner_list_denied.html");
}
IN_PROC_BROWSER_TEST_P(DocumentScanApiTest, StartScan_PermissionDenied) {
// There is a check for a valid scanner handle before the check for the
// permission from the user. Even though this tests the permission denied
// case it still needs a valid scanner handle, so set the discovery
// confirmation result.
ScannerDiscoveryRunner::SetDiscoveryConfirmationResultForTesting(true);
SetScannerInfoList({CreateTestScannerInfo()});
base::AutoReset<std::optional<bool>> testing_scope =
StartScanRunner::SetStartScanConfirmationResultForTesting(false);
RunTest("start_scan_denied.html");
}
IN_PROC_BROWSER_TEST_P(DocumentScanApiTest, PerformScan_PermissionAllowed) {
ScannerDiscoveryRunner::SetDiscoveryConfirmationResultForTesting(true);
base::AutoReset<std::optional<bool>> testing_scope =
StartScanRunner::SetStartScanConfirmationResultForTesting(true);
SetScannerInfoList({CreateTestScannerInfo()});
const std::vector<std::string> scan_data = {"img", "data", "img", "data", ""};
document_scan()->SetReadScanDataResponses(
scan_data, crosapi::mojom::ScannerOperationResult::kEndOfData);
RunTest("perform_scan.html");
// TODO(b/313494616): Load a second extension to verify (lack of)
// cross-extension handle sharing.
}
IN_PROC_BROWSER_TEST_P(DocumentScanApiTest, PerformScan_ExtensionTrusted) {
AutoTruster extension_truster(extension_registry());
// Confirmation would fail, but it doesn't matter because the extension is
// trusted.
ScannerDiscoveryRunner::SetDiscoveryConfirmationResultForTesting(false);
base::AutoReset<std::optional<bool>> testing_scope =
StartScanRunner::SetStartScanConfirmationResultForTesting(false);
SetScannerInfoList({CreateTestScannerInfo()});
const std::vector<std::string> scan_data = {"img", "data", "img", "data", ""};
document_scan()->SetReadScanDataResponses(
scan_data, crosapi::mojom::ScannerOperationResult::kEndOfData);
RunTest("perform_scan.html");
}
INSTANTIATE_TEST_SUITE_P(/**/,
DocumentScanApiTest,
testing::Values(ExtensionType::kChromeApp,
ExtensionType::kExtensionMV2,
ExtensionType::kExtensionMV3));
} // namespace extensions