blob: f7cc64be8fab8d927d975fed2095838d29ff6d0f [file] [log] [blame]
// 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 "chrome/browser/supervised_user/supervised_user_allowlist_service.h"
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/component_updater/supervised_user_whitelist_installer.h"
#include "chrome/browser/supervised_user/supervised_user_site_list.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/sync/model/sync_change.h"
#include "components/sync/model/sync_change_processor.h"
#include "components/sync/model/sync_error_factory.h"
#include "components/sync/protocol/sync.pb.h"
#include "content/public/test/browser_task_environment.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kClientId[] = "client-id";
class MockSupervisedUserWhitelistInstaller
: public component_updater::SupervisedUserWhitelistInstaller {
public:
MockSupervisedUserWhitelistInstaller() {}
~MockSupervisedUserWhitelistInstaller() override {}
const std::set<std::string>& registered_allowlists() {
return registered_allowlists_;
}
void NotifyAllowlistReady(const std::string& crx_id,
const base::string16& title,
const base::FilePath& large_icon_path,
const base::FilePath& allowlist_path) {
for (const auto& callback : ready_callbacks_)
callback.Run(crx_id, title, large_icon_path, allowlist_path);
}
// SupervisedUserWhitelistInstaller implementation:
void RegisterComponents() override {}
void Subscribe(WhitelistReadyCallback callback) override {
ready_callbacks_.push_back(callback);
}
void RegisterWhitelist(const std::string& client_id,
const std::string& crx_id,
const std::string& name) override {
EXPECT_EQ(kClientId, client_id);
EXPECT_FALSE(AllowlistIsRegistered(crx_id)) << crx_id;
registered_allowlists_.insert(crx_id);
}
void UnregisterWhitelist(const std::string& client_id,
const std::string& crx_id) override {
EXPECT_EQ(kClientId, client_id);
EXPECT_TRUE(AllowlistIsRegistered(crx_id)) << crx_id;
registered_allowlists_.erase(crx_id);
}
private:
bool AllowlistIsRegistered(const std::string& crx_id) {
return registered_allowlists_.count(crx_id) > 0;
}
std::set<std::string> registered_allowlists_;
std::vector<WhitelistReadyCallback> ready_callbacks_;
};
} // namespace
class SupervisedUserAllowlistServiceTest : public testing::Test {
public:
SupervisedUserAllowlistServiceTest()
: installer_(new MockSupervisedUserWhitelistInstaller),
service_(new SupervisedUserAllowlistService(profile_.GetPrefs(),
installer_.get(),
kClientId)) {
service_->AddSiteListsChangedCallback(
base::Bind(&SupervisedUserAllowlistServiceTest::OnSiteListsChanged,
base::Unretained(this)));
}
protected:
void PrepareInitialStateAndPreferences() {
// Create two allowlists.
DictionaryPrefUpdate update(profile_.GetPrefs(),
prefs::kSupervisedUserAllowlists);
base::DictionaryValue* dict = update.Get();
std::unique_ptr<base::DictionaryValue> allowlist_dict(
new base::DictionaryValue);
allowlist_dict->SetString("name", "Allowlist A");
dict->Set("aaaa", std::move(allowlist_dict));
allowlist_dict.reset(new base::DictionaryValue);
allowlist_dict->SetString("name", "Allowlist B");
dict->Set("bbbb", std::move(allowlist_dict));
installer_->RegisterWhitelist(kClientId, "aaaa", "Allowlist A");
installer_->RegisterWhitelist(kClientId, "bbbb", "Allowlist B");
}
void CheckFinalStateAndPreferences() {
EXPECT_EQ(2u, installer_->registered_allowlists().size());
EXPECT_EQ(1u, installer_->registered_allowlists().count("bbbb"));
EXPECT_EQ(1u, installer_->registered_allowlists().count("cccc"));
const base::DictionaryValue* dict =
profile_.GetPrefs()->GetDictionary(prefs::kSupervisedUserAllowlists);
EXPECT_EQ(2u, dict->size());
const base::DictionaryValue* allowlist_dict = nullptr;
ASSERT_TRUE(dict->GetDictionary("bbbb", &allowlist_dict));
std::string name;
ASSERT_TRUE(allowlist_dict->GetString("name", &name));
EXPECT_EQ("Allowlist B New", name);
ASSERT_TRUE(dict->GetDictionary("cccc", &allowlist_dict));
ASSERT_TRUE(allowlist_dict->GetString("name", &name));
EXPECT_EQ("Allowlist C", name);
}
const sync_pb::ManagedUserWhitelistSpecifics* FindAllowlist(
const syncer::SyncDataList& data_list,
const std::string& id) {
for (const syncer::SyncData& data : data_list) {
const sync_pb::ManagedUserWhitelistSpecifics& allowlist =
data.GetSpecifics().managed_user_whitelist();
if (allowlist.id() == id)
return &allowlist;
}
return nullptr;
}
void OnSiteListsChanged(
const std::vector<scoped_refptr<SupervisedUserSiteList>>& site_lists) {
site_lists_ = site_lists;
if (!site_lists_changed_callback_.is_null())
site_lists_changed_callback_.Run();
}
content::BrowserTaskEnvironment task_environment_;
TestingProfile profile_;
data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
std::unique_ptr<MockSupervisedUserWhitelistInstaller> installer_;
std::unique_ptr<SupervisedUserAllowlistService> service_;
std::vector<scoped_refptr<SupervisedUserSiteList>> site_lists_;
base::Closure site_lists_changed_callback_;
};
TEST_F(SupervisedUserAllowlistServiceTest, MergeEmpty) {
service_->Init();
ASSERT_TRUE(
service_->GetAllSyncDataForTesting(syncer::SUPERVISED_USER_ALLOWLISTS)
.empty());
base::Optional<syncer::ModelError> error = service_->MergeDataAndStartSyncing(
syncer::SUPERVISED_USER_ALLOWLISTS, syncer::SyncDataList(),
std::unique_ptr<syncer::SyncChangeProcessor>(),
std::unique_ptr<syncer::SyncErrorFactory>());
EXPECT_TRUE(
service_->GetAllSyncDataForTesting(syncer::SUPERVISED_USER_ALLOWLISTS)
.empty());
EXPECT_FALSE(error.has_value());
EXPECT_EQ(0u, installer_->registered_allowlists().size());
}
TEST_F(SupervisedUserAllowlistServiceTest, MergeExisting) {
PrepareInitialStateAndPreferences();
// Initialize the service. The allowlists should not be ready yet.
service_->Init();
EXPECT_EQ(0u, site_lists_.size());
// Notify that allowlist A is ready.
base::RunLoop run_loop;
site_lists_changed_callback_ = run_loop.QuitClosure();
base::FilePath test_data_dir;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
base::FilePath allowlist_path =
test_data_dir.AppendASCII("whitelists/content_pack/site_list.json");
installer_->NotifyAllowlistReady("aaaa", base::ASCIIToUTF16("Title"),
base::FilePath(), allowlist_path);
run_loop.Run();
ASSERT_EQ(1u, site_lists_.size());
EXPECT_EQ(base::ASCIIToUTF16("Title"), site_lists_[0]->title());
EXPECT_EQ(4u, site_lists_[0]->patterns().size());
// Do the initial merge. One item should be added (allowlist C), one should be
// modified (allowlist B), and one item should be removed (allowlist A).
syncer::SyncDataList initial_data;
initial_data.push_back(
SupervisedUserAllowlistService::CreateAllowlistSyncData(
"bbbb", "Allowlist B New"));
initial_data.push_back(
SupervisedUserAllowlistService::CreateAllowlistSyncData("cccc",
"Allowlist C"));
ASSERT_EQ(
2u, service_->GetAllSyncDataForTesting(syncer::SUPERVISED_USER_ALLOWLISTS)
.size());
base::Optional<syncer::ModelError> error = service_->MergeDataAndStartSyncing(
syncer::SUPERVISED_USER_ALLOWLISTS, initial_data,
std::unique_ptr<syncer::SyncChangeProcessor>(),
std::unique_ptr<syncer::SyncErrorFactory>());
EXPECT_EQ(
2u, service_->GetAllSyncDataForTesting(syncer::SUPERVISED_USER_ALLOWLISTS)
.size());
EXPECT_FALSE(error.has_value());
// Allowlist A (which was previously ready) should be removed now, and
// allowlist B was never ready.
EXPECT_EQ(0u, site_lists_.size());
CheckFinalStateAndPreferences();
}
TEST_F(SupervisedUserAllowlistServiceTest, ApplyChanges) {
PrepareInitialStateAndPreferences();
service_->Init();
// Process some changes.
syncer::SyncChangeList changes;
changes.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_ADD,
SupervisedUserAllowlistService::CreateAllowlistSyncData("cccc",
"Allowlist C")));
changes.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
SupervisedUserAllowlistService::CreateAllowlistSyncData(
"bbbb", "Allowlist B New")));
changes.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_DELETE,
SupervisedUserAllowlistService::CreateAllowlistSyncData("aaaa",
"Ignored")));
base::Optional<syncer::ModelError> error =
service_->ProcessSyncChanges(FROM_HERE, changes);
EXPECT_FALSE(error.has_value());
EXPECT_EQ(0u, site_lists_.size());
// If allowlist A now becomes ready, it should be ignored.
installer_->NotifyAllowlistReady(
"aaaa", base::ASCIIToUTF16("Title"), base::FilePath(),
base::FilePath(FILE_PATH_LITERAL("/path/to/aaaa")));
EXPECT_EQ(0u, site_lists_.size());
CheckFinalStateAndPreferences();
}