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 <stddef.h>
8
9 #include <string>
10 #include <utility>
11
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/files/file_path.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/metrics/user_metrics.h"
17 #include "base/metrics/user_metrics_action.h"
18 #include "base/strings/string_split.h"
19 #include "base/values.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_switches.h"
23 #include "chrome/common/pref_names.h"
24 #include "components/pref_registry/pref_registry_syncable.h"
25 #include "components/prefs/pref_service.h"
26 #include "components/prefs/scoped_user_pref_update.h"
27 #include "components/sync/model/sync_change.h"
28 #include "components/sync/model/sync_change_processor.h"
29 #include "components/sync/model/sync_data.h"
30 #include "components/sync/model/sync_error.h"
31 #include "components/sync/model/sync_error_factory.h"
32 #include "components/sync/protocol/sync.pb.h"
33
34 const char kName[] = "name";
35
SupervisedUserAllowlistService(PrefService * prefs,component_updater::SupervisedUserWhitelistInstaller * installer,const std::string & client_id)36 SupervisedUserAllowlistService::SupervisedUserAllowlistService(
37 PrefService* prefs,
38 component_updater::SupervisedUserWhitelistInstaller* installer,
39 const std::string& client_id)
40 : prefs_(prefs), installer_(installer), client_id_(client_id) {
41 DCHECK(prefs);
42 }
43
~SupervisedUserAllowlistService()44 SupervisedUserAllowlistService::~SupervisedUserAllowlistService() {}
45
46 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)47 void SupervisedUserAllowlistService::RegisterProfilePrefs(
48 user_prefs::PrefRegistrySyncable* registry) {
49 registry->RegisterDictionaryPref(prefs::kSupervisedUserAllowlists);
50 }
51
Init()52 void SupervisedUserAllowlistService::Init() {
53 const base::DictionaryValue* allowlists =
54 prefs_->GetDictionary(prefs::kSupervisedUserAllowlists);
55 for (base::DictionaryValue::Iterator it(*allowlists); !it.IsAtEnd();
56 it.Advance()) {
57 registered_allowlists_.insert(it.key());
58 }
59 UMA_HISTOGRAM_COUNTS_100("ManagedUsers.Whitelist.Count", allowlists->size());
60
61 // The installer can be null in some unit tests.
62 if (!installer_)
63 return;
64
65 installer_->Subscribe(
66 base::BindRepeating(&SupervisedUserAllowlistService::OnAllowlistReady,
67 weak_ptr_factory_.GetWeakPtr()));
68
69 // Register allowlists specified on the command line.
70 for (const auto& allowlist : GetAllowlistsFromCommandLine())
71 RegisterAllowlist(allowlist.first, allowlist.second, FROM_COMMAND_LINE);
72 }
73
AddSiteListsChangedCallback(const SiteListsChangedCallback & callback)74 void SupervisedUserAllowlistService::AddSiteListsChangedCallback(
75 const SiteListsChangedCallback& callback) {
76 site_lists_changed_callbacks_.push_back(callback);
77
78 std::vector<scoped_refptr<SupervisedUserSiteList>> allowlists;
79 GetLoadedAllowlists(&allowlists);
80 callback.Run(allowlists);
81 }
82
83 // static
84 std::map<std::string, std::string>
GetAllowlistsFromCommandLine()85 SupervisedUserAllowlistService::GetAllowlistsFromCommandLine() {
86 std::map<std::string, std::string> allowlists;
87 const base::CommandLine* command_line =
88 base::CommandLine::ForCurrentProcess();
89 std::string command_line_allowlists = command_line->GetSwitchValueASCII(
90 switches::kInstallSupervisedUserAllowlists);
91 std::vector<base::StringPiece> string_pieces =
92 base::SplitStringPiece(command_line_allowlists, ",",
93 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
94 for (const base::StringPiece& allowlist : string_pieces) {
95 std::string id;
96 std::string name;
97 size_t separator = allowlist.find(':');
98 if (separator != base::StringPiece::npos) {
99 id = std::string(allowlist.substr(0, separator));
100 name = std::string(allowlist.substr(separator + 1));
101 } else {
102 id = std::string(allowlist);
103 }
104
105 const bool result = allowlists.insert(std::make_pair(id, name)).second;
106 DCHECK(result);
107 }
108
109 return allowlists;
110 }
111
LoadAllowlistForTesting(const std::string & id,const base::string16 & title,const base::FilePath & path)112 void SupervisedUserAllowlistService::LoadAllowlistForTesting(
113 const std::string& id,
114 const base::string16& title,
115 const base::FilePath& path) {
116 bool result = registered_allowlists_.insert(id).second;
117 DCHECK(result);
118 OnAllowlistReady(id, title, base::FilePath(), path);
119 }
120
UnloadAllowlist(const std::string & id)121 void SupervisedUserAllowlistService::UnloadAllowlist(const std::string& id) {
122 bool result = registered_allowlists_.erase(id) > 0u;
123 DCHECK(result);
124 loaded_allowlists_.erase(id);
125 NotifyAllowlistsChanged();
126 }
127
128 // static
CreateAllowlistSyncData(const std::string & id,const std::string & name)129 syncer::SyncData SupervisedUserAllowlistService::CreateAllowlistSyncData(
130 const std::string& id,
131 const std::string& name) {
132 sync_pb::EntitySpecifics specifics;
133 sync_pb::ManagedUserWhitelistSpecifics* allowlist =
134 specifics.mutable_managed_user_whitelist();
135 allowlist->set_id(id);
136 allowlist->set_name(name);
137
138 return syncer::SyncData::CreateLocalData(id, name, specifics);
139 }
140
WaitUntilReadyToSync(base::OnceClosure done)141 void SupervisedUserAllowlistService::WaitUntilReadyToSync(
142 base::OnceClosure done) {
143 // This service handles sync events at any time.
144 std::move(done).Run();
145 }
146
147 base::Optional<syncer::ModelError>
MergeDataAndStartSyncing(syncer::ModelType type,const syncer::SyncDataList & initial_sync_data,std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,std::unique_ptr<syncer::SyncErrorFactory> error_handler)148 SupervisedUserAllowlistService::MergeDataAndStartSyncing(
149 syncer::ModelType type,
150 const syncer::SyncDataList& initial_sync_data,
151 std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
152 std::unique_ptr<syncer::SyncErrorFactory> error_handler) {
153 DCHECK_EQ(syncer::SUPERVISED_USER_ALLOWLISTS, type);
154
155 syncer::SyncChangeList change_list;
156
157 DictionaryPrefUpdate update(prefs_, prefs::kSupervisedUserAllowlists);
158 base::DictionaryValue* pref_dict = update.Get();
159 std::set<std::string> seen_ids;
160
161 for (const syncer::SyncData& sync_data : initial_sync_data) {
162 DCHECK_EQ(syncer::SUPERVISED_USER_ALLOWLISTS, sync_data.GetDataType());
163 const sync_pb::ManagedUserWhitelistSpecifics& allowlist =
164 sync_data.GetSpecifics().managed_user_whitelist();
165 std::string id = allowlist.id();
166 std::string name = allowlist.name();
167 seen_ids.insert(id);
168 base::DictionaryValue* dict = nullptr;
169 if (pref_dict->GetDictionary(id, &dict)) {
170 std::string old_name;
171 bool result = dict->GetString(kName, &old_name);
172 DCHECK(result);
173 if (name != old_name) {
174 SetAllowlistProperties(dict, allowlist);
175 }
176 } else {
177 AddNewAllowlist(pref_dict, allowlist);
178 }
179 }
180
181 std::set<std::string> ids_to_remove;
182 for (base::DictionaryValue::Iterator it(*pref_dict); !it.IsAtEnd();
183 it.Advance()) {
184 if (seen_ids.find(it.key()) == seen_ids.end())
185 ids_to_remove.insert(it.key());
186 }
187
188 for (const std::string& id : ids_to_remove)
189 RemoveAllowlist(pref_dict, id);
190
191 // Notify if allowlists have been uninstalled. We will notify about newly
192 // added allowlists later, when they are actually available
193 // (in OnAllowlistLoaded).
194 if (!ids_to_remove.empty())
195 NotifyAllowlistsChanged();
196
197 // The function does not generate any errors, so it can always return
198 // base::nullopt.
199 return base::nullopt;
200 }
201
StopSyncing(syncer::ModelType type)202 void SupervisedUserAllowlistService::StopSyncing(syncer::ModelType type) {
203 DCHECK_EQ(syncer::SUPERVISED_USER_ALLOWLISTS, type);
204 }
205
GetAllSyncDataForTesting(syncer::ModelType type) const206 syncer::SyncDataList SupervisedUserAllowlistService::GetAllSyncDataForTesting(
207 syncer::ModelType type) const {
208 syncer::SyncDataList sync_data;
209 const base::DictionaryValue* allowlists =
210 prefs_->GetDictionary(prefs::kSupervisedUserAllowlists);
211 for (base::DictionaryValue::Iterator it(*allowlists); !it.IsAtEnd();
212 it.Advance()) {
213 const std::string& id = it.key();
214 const base::DictionaryValue* dict = nullptr;
215 it.value().GetAsDictionary(&dict);
216 std::string name;
217 bool result = dict->GetString(kName, &name);
218 DCHECK(result);
219 sync_pb::EntitySpecifics specifics;
220 sync_pb::ManagedUserWhitelistSpecifics* allowlist =
221 specifics.mutable_managed_user_whitelist();
222 allowlist->set_id(id);
223 allowlist->set_name(name);
224 sync_data.push_back(syncer::SyncData::CreateLocalData(id, name, specifics));
225 }
226 return sync_data;
227 }
228
229 base::Optional<syncer::ModelError>
ProcessSyncChanges(const base::Location & from_here,const syncer::SyncChangeList & change_list)230 SupervisedUserAllowlistService::ProcessSyncChanges(
231 const base::Location& from_here,
232 const syncer::SyncChangeList& change_list) {
233 bool allowlists_removed = false;
234 DictionaryPrefUpdate update(prefs_, prefs::kSupervisedUserAllowlists);
235 base::DictionaryValue* pref_dict = update.Get();
236 for (const syncer::SyncChange& sync_change : change_list) {
237 syncer::SyncData data = sync_change.sync_data();
238 DCHECK_EQ(syncer::SUPERVISED_USER_ALLOWLISTS, data.GetDataType());
239 const sync_pb::ManagedUserWhitelistSpecifics& allowlist =
240 data.GetSpecifics().managed_user_whitelist();
241 std::string id = allowlist.id();
242 switch (sync_change.change_type()) {
243 case syncer::SyncChange::ACTION_ADD: {
244 DCHECK(!pref_dict->HasKey(id)) << id;
245 AddNewAllowlist(pref_dict, allowlist);
246 break;
247 }
248 case syncer::SyncChange::ACTION_UPDATE: {
249 base::DictionaryValue* dict = nullptr;
250 pref_dict->GetDictionaryWithoutPathExpansion(id, &dict);
251 SetAllowlistProperties(dict, allowlist);
252 break;
253 }
254 case syncer::SyncChange::ACTION_DELETE: {
255 DCHECK(pref_dict->HasKey(id)) << id;
256 RemoveAllowlist(pref_dict, id);
257 allowlists_removed = true;
258 break;
259 }
260 case syncer::SyncChange::ACTION_INVALID: {
261 NOTREACHED();
262 break;
263 }
264 }
265 }
266
267 if (allowlists_removed)
268 NotifyAllowlistsChanged();
269
270 return base::nullopt;
271 }
272
AddNewAllowlist(base::DictionaryValue * pref_dict,const sync_pb::ManagedUserWhitelistSpecifics & allowlist)273 void SupervisedUserAllowlistService::AddNewAllowlist(
274 base::DictionaryValue* pref_dict,
275 const sync_pb::ManagedUserWhitelistSpecifics& allowlist) {
276 base::RecordAction(base::UserMetricsAction("ManagedUsers_Whitelist_Added"));
277
278 RegisterAllowlist(allowlist.id(), allowlist.name(), FROM_SYNC);
279 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
280 SetAllowlistProperties(dict.get(), allowlist);
281 pref_dict->SetWithoutPathExpansion(allowlist.id(), std::move(dict));
282 }
283
SetAllowlistProperties(base::DictionaryValue * dict,const sync_pb::ManagedUserWhitelistSpecifics & allowlist)284 void SupervisedUserAllowlistService::SetAllowlistProperties(
285 base::DictionaryValue* dict,
286 const sync_pb::ManagedUserWhitelistSpecifics& allowlist) {
287 dict->SetString(kName, allowlist.name());
288 }
289
RemoveAllowlist(base::DictionaryValue * pref_dict,const std::string & id)290 void SupervisedUserAllowlistService::RemoveAllowlist(
291 base::DictionaryValue* pref_dict,
292 const std::string& id) {
293 base::RecordAction(base::UserMetricsAction("ManagedUsers_Whitelist_Removed"));
294
295 pref_dict->RemoveKey(id);
296 installer_->UnregisterWhitelist(client_id_, id);
297 UnloadAllowlist(id);
298 }
299
RegisterAllowlist(const std::string & id,const std::string & name,AllowlistSource source)300 void SupervisedUserAllowlistService::RegisterAllowlist(const std::string& id,
301 const std::string& name,
302 AllowlistSource source) {
303 bool result = registered_allowlists_.insert(id).second;
304 DCHECK(result);
305
306 // Using an empty client ID for allowlists installed from the command line
307 // causes the installer to not persist the installation, so the allowlist will
308 // be removed the next time the browser is started without the command line
309 // flag.
310 installer_->RegisterWhitelist(
311 source == FROM_COMMAND_LINE ? std::string() : client_id_, id, name);
312 }
313
GetLoadedAllowlists(std::vector<scoped_refptr<SupervisedUserSiteList>> * allowlists)314 void SupervisedUserAllowlistService::GetLoadedAllowlists(
315 std::vector<scoped_refptr<SupervisedUserSiteList>>* allowlists) {
316 for (const auto& allowlist : loaded_allowlists_)
317 allowlists->push_back(allowlist.second);
318 }
319
NotifyAllowlistsChanged()320 void SupervisedUserAllowlistService::NotifyAllowlistsChanged() {
321 std::vector<scoped_refptr<SupervisedUserSiteList>> allowlists;
322 GetLoadedAllowlists(&allowlists);
323
324 for (const auto& callback : site_lists_changed_callbacks_)
325 callback.Run(allowlists);
326 }
327
OnAllowlistReady(const std::string & id,const base::string16 & title,const base::FilePath & large_icon_path,const base::FilePath & allowlist_path)328 void SupervisedUserAllowlistService::OnAllowlistReady(
329 const std::string& id,
330 const base::string16& title,
331 const base::FilePath& large_icon_path,
332 const base::FilePath& allowlist_path) {
333 // If we did not register the allowlist or it has been unregistered in the
334 // mean time, ignore it.
335 if (registered_allowlists_.count(id) == 0u)
336 return;
337
338 SupervisedUserSiteList::Load(
339 id, title, large_icon_path, allowlist_path,
340 base::Bind(&SupervisedUserAllowlistService::OnAllowlistLoaded,
341 weak_ptr_factory_.GetWeakPtr(), id));
342 }
343
OnAllowlistLoaded(const std::string & id,const scoped_refptr<SupervisedUserSiteList> & allowlist)344 void SupervisedUserAllowlistService::OnAllowlistLoaded(
345 const std::string& id,
346 const scoped_refptr<SupervisedUserSiteList>& allowlist) {
347 if (!allowlist) {
348 LOG(WARNING) << "Couldn't load allowlist " << id;
349 return;
350 }
351 // If the allowlist has been unregistered in the mean time, ignore it.
352 if (registered_allowlists_.count(id) == 0u)
353 return;
354
355 loaded_allowlists_[id] = allowlist;
356 NotifyAllowlistsChanged();
357 }
358