// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/bind.h" #include "base/location.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/thread_task_runner_handle.h" #include "chrome/browser/extensions/extension_install_checker.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { namespace { const BlacklistState kBlacklistStateError = BLACKLISTED_MALWARE; const char kDummyRequirementsError[] = "Requirements error"; const char kDummyPolicyError[] = "Cannot install extension"; const char kDummyPolicyError2[] = "Another policy error"; const char kDummyRequirementsError2[] = "Another requirements error"; const BlacklistState kBlacklistState2 = BLACKLISTED_SECURITY_VULNERABILITY; } // namespace // Stubs most of the checks since we are interested in validating the logic in // the install checker. This class implements a synchronous version of all // checks. class ExtensionInstallCheckerForTest : public ExtensionInstallChecker { public: ExtensionInstallCheckerForTest() : ExtensionInstallChecker(NULL), requirements_check_called_(false), blacklist_check_called_(false), policy_check_called_(false), blacklist_state_(NOT_BLACKLISTED) {} ~ExtensionInstallCheckerForTest() override {} void set_requirements_error(const std::string& error) { requirements_error_ = error; } void set_policy_check_error(const std::string& error) { policy_check_error_ = error; } void set_blacklist_state(BlacklistState state) { blacklist_state_ = state; } bool requirements_check_called() const { return requirements_check_called_; } bool blacklist_check_called() const { return blacklist_check_called_; } bool policy_check_called() const { return policy_check_called_; } void MockCheckRequirements(int sequence_number) { std::vector errors; if (!requirements_error_.empty()) errors.push_back(requirements_error_); OnRequirementsCheckDone(sequence_number, errors); } void MockCheckBlacklistState(int sequence_number) { OnBlacklistStateCheckDone(sequence_number, blacklist_state_); } protected: void CheckRequirements() override { requirements_check_called_ = true; MockCheckRequirements(current_sequence_number()); } void CheckManagementPolicy() override { policy_check_called_ = true; OnManagementPolicyCheckDone(policy_check_error_.empty(), policy_check_error_); } void CheckBlacklistState() override { blacklist_check_called_ = true; MockCheckBlacklistState(current_sequence_number()); } void ResetResults() override { ExtensionInstallChecker::ResetResults(); requirements_check_called_ = false; blacklist_check_called_ = false; policy_check_called_ = false; } bool requirements_check_called_; bool blacklist_check_called_; bool policy_check_called_; // Dummy errors for testing. std::string requirements_error_; std::string policy_check_error_; BlacklistState blacklist_state_; }; // This class implements asynchronous mocks of the requirements and blacklist // checks. class ExtensionInstallCheckerAsync : public ExtensionInstallCheckerForTest { protected: void CheckRequirements() override { requirements_check_called_ = true; base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&ExtensionInstallCheckerForTest::MockCheckRequirements, base::Unretained(this), current_sequence_number())); } void CheckBlacklistState() override { blacklist_check_called_ = true; base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&ExtensionInstallCheckerForTest::MockCheckBlacklistState, base::Unretained(this), current_sequence_number())); } }; class CheckObserver { public: CheckObserver() : result_(0), call_count_(0) {} int result() const { return result_; } int call_count() const { return call_count_; } void OnChecksComplete(int checks_failed) { result_ = checks_failed; ++call_count_; } void Wait() { if (call_count_) return; base::RunLoop().RunUntilIdle(); } private: int result_; int call_count_; }; class ExtensionInstallCheckerTest : public testing::Test { public: ExtensionInstallCheckerTest() {} ~ExtensionInstallCheckerTest() override {} void RunSecondInvocation(ExtensionInstallCheckerForTest* checker, int checks_failed) { EXPECT_GT(checks_failed, 0); EXPECT_FALSE(checker->is_running()); ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker); // Set up different return values. checker->set_blacklist_state(kBlacklistState2); checker->set_policy_check_error(kDummyPolicyError2); checker->set_requirements_error(kDummyRequirementsError2); // Run the install checker again and ensure the second set of return values // is received. checker->Start( ExtensionInstallChecker::CHECK_ALL, false /* fail fast */, base::Bind(&ExtensionInstallCheckerTest::ValidateSecondInvocation, base::Unretained(this), checker)); } void ValidateSecondInvocation(ExtensionInstallCheckerForTest* checker, int checks_failed) { EXPECT_FALSE(checker->is_running()); EXPECT_EQ(ExtensionInstallChecker::CHECK_REQUIREMENTS | ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY, checks_failed); ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker); EXPECT_EQ(kBlacklistState2, checker->blacklist_state()); ExpectPolicyError(kDummyPolicyError2, *checker); ExpectRequirementsError(kDummyRequirementsError2, *checker); } protected: void SetAllErrors(ExtensionInstallCheckerForTest* checker) { checker->set_blacklist_state(kBlacklistStateError); checker->set_policy_check_error(kDummyPolicyError); checker->set_requirements_error(kDummyRequirementsError); } void ValidateExpectedCalls(int call_mask, const ExtensionInstallCheckerForTest& checker) { bool expect_blacklist_checked = (call_mask & ExtensionInstallChecker::CHECK_BLACKLIST) != 0; bool expect_requirements_checked = (call_mask & ExtensionInstallChecker::CHECK_REQUIREMENTS) != 0; bool expect_policy_checked = (call_mask & ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY) != 0; EXPECT_EQ(expect_blacklist_checked, checker.blacklist_check_called()); EXPECT_EQ(expect_policy_checked, checker.policy_check_called()); EXPECT_EQ(expect_requirements_checked, checker.requirements_check_called()); } void ExpectRequirementsPass(const ExtensionInstallCheckerForTest& checker) { EXPECT_TRUE(checker.requirement_errors().empty()); } void ExpectRequirementsError(const char* expected_error, const ExtensionInstallCheckerForTest& checker) { EXPECT_FALSE(checker.requirement_errors().empty()); EXPECT_EQ(std::string(expected_error), checker.requirement_errors().front()); } void ExpectRequirementsError(const ExtensionInstallCheckerForTest& checker) { ExpectRequirementsError(kDummyRequirementsError, checker); } void ExpectBlacklistPass(const ExtensionInstallCheckerForTest& checker) { EXPECT_EQ(NOT_BLACKLISTED, checker.blacklist_state()); } void ExpectBlacklistError(const ExtensionInstallCheckerForTest& checker) { EXPECT_EQ(kBlacklistStateError, checker.blacklist_state()); } void ExpectPolicyPass(const ExtensionInstallCheckerForTest& checker) { EXPECT_TRUE(checker.policy_allows_load()); EXPECT_TRUE(checker.policy_error().empty()); } void ExpectPolicyError(const char* expected_error, const ExtensionInstallCheckerForTest& checker) { EXPECT_FALSE(checker.policy_allows_load()); EXPECT_FALSE(checker.policy_error().empty()); EXPECT_EQ(std::string(expected_error), checker.policy_error()); } void ExpectPolicyError(const ExtensionInstallCheckerForTest& checker) { ExpectPolicyError(kDummyPolicyError, checker); } void RunChecker(ExtensionInstallCheckerForTest* checker, bool fail_fast, int checks_to_run, int expected_checks_run, int expected_result) { CheckObserver observer; checker->Start(checks_to_run, fail_fast, base::Bind(&CheckObserver::OnChecksComplete, base::Unretained(&observer))); observer.Wait(); EXPECT_FALSE(checker->is_running()); EXPECT_EQ(expected_result, observer.result()); EXPECT_EQ(1, observer.call_count()); ValidateExpectedCalls(expected_checks_run, *checker); } void DoRunAllChecksPass(ExtensionInstallCheckerForTest* checker) { RunChecker(checker, false /* fail fast */, ExtensionInstallChecker::CHECK_ALL, ExtensionInstallChecker::CHECK_ALL, 0); ExpectRequirementsPass(*checker); ExpectPolicyPass(*checker); ExpectBlacklistPass(*checker); } void DoRunAllChecksFail(ExtensionInstallCheckerForTest* checker) { SetAllErrors(checker); RunChecker(checker, false /* fail fast */, ExtensionInstallChecker::CHECK_ALL, ExtensionInstallChecker::CHECK_ALL, ExtensionInstallChecker::CHECK_ALL); ExpectRequirementsError(*checker); ExpectPolicyError(*checker); ExpectBlacklistError(*checker); } void DoRunSubsetOfChecks(ExtensionInstallCheckerForTest* checker) { // Test check set 1. int tests_to_run = ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY | ExtensionInstallChecker::CHECK_REQUIREMENTS; SetAllErrors(checker); RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run); ExpectRequirementsError(*checker); ExpectPolicyError(*checker); ExpectBlacklistPass(*checker); // Test check set 2. tests_to_run = ExtensionInstallChecker::CHECK_BLACKLIST | ExtensionInstallChecker::CHECK_REQUIREMENTS; SetAllErrors(checker); RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run); ExpectRequirementsError(*checker); ExpectPolicyPass(*checker); ExpectBlacklistError(*checker); // Test a single check. tests_to_run = ExtensionInstallChecker::CHECK_BLACKLIST; SetAllErrors(checker); RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run); ExpectRequirementsPass(*checker); ExpectPolicyPass(*checker); ExpectBlacklistError(*checker); } private: // A message loop is required for the asynchronous tests. base::MessageLoop message_loop; }; class ExtensionInstallCheckerMultipleInvocationTest : public ExtensionInstallCheckerTest { public: ExtensionInstallCheckerMultipleInvocationTest() : callback_count_(0) {} ~ExtensionInstallCheckerMultipleInvocationTest() override {} void RunSecondInvocation(ExtensionInstallCheckerForTest* checker, int checks_failed) { ASSERT_EQ(0, callback_count_); ++callback_count_; EXPECT_FALSE(checker->is_running()); EXPECT_GT(checks_failed, 0); ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker); // Set up different return values. checker->set_blacklist_state(kBlacklistState2); checker->set_policy_check_error(kDummyPolicyError2); checker->set_requirements_error(kDummyRequirementsError2); // Run the install checker again and ensure the second set of return values // is received. checker->Start(ExtensionInstallChecker::CHECK_ALL, false /* fail fast */, base::Bind(&ExtensionInstallCheckerMultipleInvocationTest:: ValidateSecondInvocation, base::Unretained(this), checker)); } void ValidateSecondInvocation(ExtensionInstallCheckerForTest* checker, int checks_failed) { ASSERT_EQ(1, callback_count_); EXPECT_FALSE(checker->is_running()); EXPECT_EQ(ExtensionInstallChecker::CHECK_REQUIREMENTS | ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY, checks_failed); ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker); EXPECT_EQ(kBlacklistState2, checker->blacklist_state()); ExpectPolicyError(kDummyPolicyError2, *checker); ExpectRequirementsError(kDummyRequirementsError2, *checker); } private: int callback_count_; }; // Test the case where all tests pass. TEST_F(ExtensionInstallCheckerTest, AllSucceeded) { ExtensionInstallCheckerForTest sync_checker; DoRunAllChecksPass(&sync_checker); ExtensionInstallCheckerAsync async_checker; DoRunAllChecksPass(&async_checker); } // Test the case where all tests fail. TEST_F(ExtensionInstallCheckerTest, AllFailed) { ExtensionInstallCheckerForTest sync_checker; DoRunAllChecksFail(&sync_checker); ExtensionInstallCheckerAsync async_checker; DoRunAllChecksFail(&async_checker); } // Test running only a subset of tests. TEST_F(ExtensionInstallCheckerTest, RunSubsetOfChecks) { ExtensionInstallCheckerForTest sync_checker; ExtensionInstallCheckerAsync async_checker; DoRunSubsetOfChecks(&sync_checker); DoRunSubsetOfChecks(&async_checker); } // Test fail fast with synchronous callbacks. TEST_F(ExtensionInstallCheckerTest, FailFastSync) { // This test assumes some internal knowledge of the implementation - that // the policy check runs first. ExtensionInstallCheckerForTest checker; SetAllErrors(&checker); RunChecker(&checker, true /* fail fast */, ExtensionInstallChecker::CHECK_ALL, ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY, ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY); ExpectRequirementsPass(checker); ExpectPolicyError(checker); ExpectBlacklistPass(checker); // This test assumes some internal knowledge of the implementation - that // the requirements check runs before the blacklist check. SetAllErrors(&checker); RunChecker(&checker, true /* fail fast */, ExtensionInstallChecker::CHECK_REQUIREMENTS | ExtensionInstallChecker::CHECK_BLACKLIST, ExtensionInstallChecker::CHECK_REQUIREMENTS, ExtensionInstallChecker::CHECK_REQUIREMENTS); ExpectRequirementsError(checker); ExpectPolicyPass(checker); ExpectBlacklistPass(checker); } // Test fail fast with asynchronous callbacks. TEST_F(ExtensionInstallCheckerTest, FailFastAsync) { // This test assumes some internal knowledge of the implementation - that // the requirements check runs before the blacklist check. Both checks should // be called, but the requirements check callback arrives first and the // blacklist result will be discarded. ExtensionInstallCheckerAsync checker; SetAllErrors(&checker); // The policy check is synchronous and needs to pass for the other tests to // run. checker.set_policy_check_error(std::string()); RunChecker(&checker, true /* fail fast */, ExtensionInstallChecker::CHECK_ALL, ExtensionInstallChecker::CHECK_ALL, ExtensionInstallChecker::CHECK_REQUIREMENTS); ExpectRequirementsError(checker); ExpectPolicyPass(checker); ExpectBlacklistPass(checker); } // Test multiple invocations of the install checker. Wait for all checks to // complete. TEST_F(ExtensionInstallCheckerMultipleInvocationTest, CompleteAll) { ExtensionInstallCheckerAsync checker; SetAllErrors(&checker); // Start the second check as soon as the callback of the first run is invoked. checker.Start( ExtensionInstallChecker::CHECK_ALL, false /* fail fast */, base::Bind( &ExtensionInstallCheckerMultipleInvocationTest::RunSecondInvocation, base::Unretained(this), &checker)); base::RunLoop().RunUntilIdle(); } // Test multiple invocations of the install checker and fail fast. TEST_F(ExtensionInstallCheckerMultipleInvocationTest, FailFast) { ExtensionInstallCheckerAsync checker; SetAllErrors(&checker); // The policy check is synchronous and needs to pass for the other tests to // run. checker.set_policy_check_error(std::string()); // Start the second check as soon as the callback of the first run is invoked. checker.Start( ExtensionInstallChecker::CHECK_ALL, true /* fail fast */, base::Bind( &ExtensionInstallCheckerMultipleInvocationTest::RunSecondInvocation, base::Unretained(this), &checker)); base::RunLoop().RunUntilIdle(); } } // namespace extensions