1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/supervised_user/supervised_user_allowlist_service.h"
6 
7 #include <map>
8 #include <memory>
9 #include <set>
10 #include <string>
11 #include <utility>
12 
13 #include "base/bind.h"
14 #include "base/callback.h"
15 #include "base/path_service.h"
16 #include "base/run_loop.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "build/build_config.h"
20 #include "chrome/browser/component_updater/supervised_user_whitelist_installer.h"
21 #include "chrome/browser/supervised_user/supervised_user_site_list.h"
22 #include "chrome/common/chrome_paths.h"
23 #include "chrome/common/pref_names.h"
24 #include "chrome/test/base/testing_profile.h"
25 #include "components/prefs/scoped_user_pref_update.h"
26 #include "components/sync/model/sync_change.h"
27 #include "components/sync/model/sync_change_processor.h"
28 #include "components/sync/model/sync_error_factory.h"
29 #include "components/sync/protocol/sync.pb.h"
30 #include "content/public/test/browser_task_environment.h"
31 #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 
34 namespace {
35 
36 const char kClientId[] = "client-id";
37 
38 class MockSupervisedUserWhitelistInstaller
39     : public component_updater::SupervisedUserWhitelistInstaller {
40  public:
MockSupervisedUserWhitelistInstaller()41   MockSupervisedUserWhitelistInstaller() {}
~MockSupervisedUserWhitelistInstaller()42   ~MockSupervisedUserWhitelistInstaller() override {}
43 
registered_allowlists()44   const std::set<std::string>& registered_allowlists() {
45     return registered_allowlists_;
46   }
47 
NotifyAllowlistReady(const std::string & crx_id,const base::string16 & title,const base::FilePath & large_icon_path,const base::FilePath & allowlist_path)48   void NotifyAllowlistReady(const std::string& crx_id,
49                             const base::string16& title,
50                             const base::FilePath& large_icon_path,
51                             const base::FilePath& allowlist_path) {
52     for (const auto& callback : ready_callbacks_)
53       callback.Run(crx_id, title, large_icon_path, allowlist_path);
54   }
55 
56   // SupervisedUserWhitelistInstaller implementation:
RegisterComponents()57   void RegisterComponents() override {}
58 
Subscribe(WhitelistReadyCallback callback)59   void Subscribe(WhitelistReadyCallback callback) override {
60     ready_callbacks_.push_back(callback);
61   }
62 
RegisterWhitelist(const std::string & client_id,const std::string & crx_id,const std::string & name)63   void RegisterWhitelist(const std::string& client_id,
64                          const std::string& crx_id,
65                          const std::string& name) override {
66     EXPECT_EQ(kClientId, client_id);
67     EXPECT_FALSE(AllowlistIsRegistered(crx_id)) << crx_id;
68     registered_allowlists_.insert(crx_id);
69   }
70 
UnregisterWhitelist(const std::string & client_id,const std::string & crx_id)71   void UnregisterWhitelist(const std::string& client_id,
72                            const std::string& crx_id) override {
73     EXPECT_EQ(kClientId, client_id);
74     EXPECT_TRUE(AllowlistIsRegistered(crx_id)) << crx_id;
75     registered_allowlists_.erase(crx_id);
76   }
77 
78  private:
AllowlistIsRegistered(const std::string & crx_id)79   bool AllowlistIsRegistered(const std::string& crx_id) {
80     return registered_allowlists_.count(crx_id) > 0;
81   }
82 
83   std::set<std::string> registered_allowlists_;
84   std::vector<WhitelistReadyCallback> ready_callbacks_;
85 };
86 
87 }  // namespace
88 
89 class SupervisedUserAllowlistServiceTest : public testing::Test {
90  public:
SupervisedUserAllowlistServiceTest()91   SupervisedUserAllowlistServiceTest()
92       : installer_(new MockSupervisedUserWhitelistInstaller),
93         service_(new SupervisedUserAllowlistService(profile_.GetPrefs(),
94                                                     installer_.get(),
95                                                     kClientId)) {
96     service_->AddSiteListsChangedCallback(
97         base::Bind(&SupervisedUserAllowlistServiceTest::OnSiteListsChanged,
98                    base::Unretained(this)));
99   }
100 
101  protected:
PrepareInitialStateAndPreferences()102   void PrepareInitialStateAndPreferences() {
103     // Create two allowlists.
104     DictionaryPrefUpdate update(profile_.GetPrefs(),
105                                 prefs::kSupervisedUserAllowlists);
106     base::DictionaryValue* dict = update.Get();
107 
108     std::unique_ptr<base::DictionaryValue> allowlist_dict(
109         new base::DictionaryValue);
110     allowlist_dict->SetString("name", "Allowlist A");
111     dict->Set("aaaa", std::move(allowlist_dict));
112 
113     allowlist_dict.reset(new base::DictionaryValue);
114     allowlist_dict->SetString("name", "Allowlist B");
115     dict->Set("bbbb", std::move(allowlist_dict));
116 
117     installer_->RegisterWhitelist(kClientId, "aaaa", "Allowlist A");
118     installer_->RegisterWhitelist(kClientId, "bbbb", "Allowlist B");
119   }
120 
CheckFinalStateAndPreferences()121   void CheckFinalStateAndPreferences() {
122     EXPECT_EQ(2u, installer_->registered_allowlists().size());
123     EXPECT_EQ(1u, installer_->registered_allowlists().count("bbbb"));
124     EXPECT_EQ(1u, installer_->registered_allowlists().count("cccc"));
125 
126     const base::DictionaryValue* dict =
127         profile_.GetPrefs()->GetDictionary(prefs::kSupervisedUserAllowlists);
128     EXPECT_EQ(2u, dict->size());
129     const base::DictionaryValue* allowlist_dict = nullptr;
130     ASSERT_TRUE(dict->GetDictionary("bbbb", &allowlist_dict));
131     std::string name;
132     ASSERT_TRUE(allowlist_dict->GetString("name", &name));
133     EXPECT_EQ("Allowlist B New", name);
134     ASSERT_TRUE(dict->GetDictionary("cccc", &allowlist_dict));
135     ASSERT_TRUE(allowlist_dict->GetString("name", &name));
136     EXPECT_EQ("Allowlist C", name);
137   }
138 
FindAllowlist(const syncer::SyncDataList & data_list,const std::string & id)139   const sync_pb::ManagedUserWhitelistSpecifics* FindAllowlist(
140       const syncer::SyncDataList& data_list,
141       const std::string& id) {
142     for (const syncer::SyncData& data : data_list) {
143       const sync_pb::ManagedUserWhitelistSpecifics& allowlist =
144           data.GetSpecifics().managed_user_whitelist();
145       if (allowlist.id() == id)
146         return &allowlist;
147     }
148     return nullptr;
149   }
150 
OnSiteListsChanged(const std::vector<scoped_refptr<SupervisedUserSiteList>> & site_lists)151   void OnSiteListsChanged(
152       const std::vector<scoped_refptr<SupervisedUserSiteList>>& site_lists) {
153     site_lists_ = site_lists;
154     if (!site_lists_changed_callback_.is_null())
155       site_lists_changed_callback_.Run();
156   }
157 
158   content::BrowserTaskEnvironment task_environment_;
159   TestingProfile profile_;
160   data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
161 
162   std::unique_ptr<MockSupervisedUserWhitelistInstaller> installer_;
163   std::unique_ptr<SupervisedUserAllowlistService> service_;
164 
165   std::vector<scoped_refptr<SupervisedUserSiteList>> site_lists_;
166   base::Closure site_lists_changed_callback_;
167 };
168 
TEST_F(SupervisedUserAllowlistServiceTest,MergeEmpty)169 TEST_F(SupervisedUserAllowlistServiceTest, MergeEmpty) {
170   service_->Init();
171 
172   ASSERT_TRUE(
173       service_->GetAllSyncDataForTesting(syncer::SUPERVISED_USER_ALLOWLISTS)
174           .empty());
175   base::Optional<syncer::ModelError> error = service_->MergeDataAndStartSyncing(
176       syncer::SUPERVISED_USER_ALLOWLISTS, syncer::SyncDataList(),
177       std::unique_ptr<syncer::SyncChangeProcessor>(),
178       std::unique_ptr<syncer::SyncErrorFactory>());
179   EXPECT_TRUE(
180       service_->GetAllSyncDataForTesting(syncer::SUPERVISED_USER_ALLOWLISTS)
181           .empty());
182   EXPECT_FALSE(error.has_value());
183 
184   EXPECT_EQ(0u, installer_->registered_allowlists().size());
185 }
186 
TEST_F(SupervisedUserAllowlistServiceTest,MergeExisting)187 TEST_F(SupervisedUserAllowlistServiceTest, MergeExisting) {
188   PrepareInitialStateAndPreferences();
189 
190   // Initialize the service. The allowlists should not be ready yet.
191   service_->Init();
192   EXPECT_EQ(0u, site_lists_.size());
193 
194   // Notify that allowlist A is ready.
195   base::RunLoop run_loop;
196   site_lists_changed_callback_ = run_loop.QuitClosure();
197   base::FilePath test_data_dir;
198   ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
199   base::FilePath allowlist_path =
200       test_data_dir.AppendASCII("whitelists/content_pack/site_list.json");
201   installer_->NotifyAllowlistReady("aaaa", base::ASCIIToUTF16("Title"),
202                                    base::FilePath(), allowlist_path);
203   run_loop.Run();
204 
205   ASSERT_EQ(1u, site_lists_.size());
206   EXPECT_EQ(base::ASCIIToUTF16("Title"), site_lists_[0]->title());
207   EXPECT_EQ(4u, site_lists_[0]->patterns().size());
208 
209   // Do the initial merge. One item should be added (allowlist C), one should be
210   // modified (allowlist B), and one item should be removed (allowlist A).
211   syncer::SyncDataList initial_data;
212   initial_data.push_back(
213       SupervisedUserAllowlistService::CreateAllowlistSyncData(
214           "bbbb", "Allowlist B New"));
215   initial_data.push_back(
216       SupervisedUserAllowlistService::CreateAllowlistSyncData("cccc",
217                                                               "Allowlist C"));
218   ASSERT_EQ(
219       2u, service_->GetAllSyncDataForTesting(syncer::SUPERVISED_USER_ALLOWLISTS)
220               .size());
221   base::Optional<syncer::ModelError> error = service_->MergeDataAndStartSyncing(
222       syncer::SUPERVISED_USER_ALLOWLISTS, initial_data,
223       std::unique_ptr<syncer::SyncChangeProcessor>(),
224       std::unique_ptr<syncer::SyncErrorFactory>());
225   EXPECT_EQ(
226       2u, service_->GetAllSyncDataForTesting(syncer::SUPERVISED_USER_ALLOWLISTS)
227               .size());
228   EXPECT_FALSE(error.has_value());
229 
230   // Allowlist A (which was previously ready) should be removed now, and
231   // allowlist B was never ready.
232   EXPECT_EQ(0u, site_lists_.size());
233 
234   CheckFinalStateAndPreferences();
235 }
236 
TEST_F(SupervisedUserAllowlistServiceTest,ApplyChanges)237 TEST_F(SupervisedUserAllowlistServiceTest, ApplyChanges) {
238   PrepareInitialStateAndPreferences();
239 
240   service_->Init();
241 
242   // Process some changes.
243   syncer::SyncChangeList changes;
244   changes.push_back(syncer::SyncChange(
245       FROM_HERE, syncer::SyncChange::ACTION_ADD,
246       SupervisedUserAllowlistService::CreateAllowlistSyncData("cccc",
247                                                               "Allowlist C")));
248   changes.push_back(syncer::SyncChange(
249       FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
250       SupervisedUserAllowlistService::CreateAllowlistSyncData(
251           "bbbb", "Allowlist B New")));
252   changes.push_back(syncer::SyncChange(
253       FROM_HERE, syncer::SyncChange::ACTION_DELETE,
254       SupervisedUserAllowlistService::CreateAllowlistSyncData("aaaa",
255                                                               "Ignored")));
256   base::Optional<syncer::ModelError> error =
257       service_->ProcessSyncChanges(FROM_HERE, changes);
258   EXPECT_FALSE(error.has_value());
259 
260   EXPECT_EQ(0u, site_lists_.size());
261 
262   // If allowlist A now becomes ready, it should be ignored.
263   installer_->NotifyAllowlistReady(
264       "aaaa", base::ASCIIToUTF16("Title"), base::FilePath(),
265       base::FilePath(FILE_PATH_LITERAL("/path/to/aaaa")));
266   EXPECT_EQ(0u, site_lists_.size());
267 
268   CheckFinalStateAndPreferences();
269 }
270