blob: 083d533d669ad485c62f263a3e9d351f267205bf [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_whitelist_service.h"
#include <stddef.h>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/string_split.h"
#include "base/values.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_switches.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.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_data.h"
#include "components/sync/model/sync_error.h"
#include "components/sync/model/sync_error_factory.h"
#include "components/sync/model/sync_merge_result.h"
#include "components/sync/protocol/sync.pb.h"
const char kName[] = "name";
SupervisedUserWhitelistService::SupervisedUserWhitelistService(
PrefService* prefs,
component_updater::SupervisedUserWhitelistInstaller* installer,
const std::string& client_id)
: prefs_(prefs),
installer_(installer),
client_id_(client_id),
weak_ptr_factory_(this) {
DCHECK(prefs);
}
SupervisedUserWhitelistService::~SupervisedUserWhitelistService() {
}
// static
void SupervisedUserWhitelistService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(prefs::kSupervisedUserWhitelists);
}
void SupervisedUserWhitelistService::Init() {
const base::DictionaryValue* whitelists =
prefs_->GetDictionary(prefs::kSupervisedUserWhitelists);
for (base::DictionaryValue::Iterator it(*whitelists); !it.IsAtEnd();
it.Advance()) {
registered_whitelists_.insert(it.key());
}
UMA_HISTOGRAM_COUNTS_100("ManagedUsers.Whitelist.Count", whitelists->size());
// The installer can be null in some unit tests.
if (!installer_)
return;
installer_->Subscribe(
base::Bind(&SupervisedUserWhitelistService::OnWhitelistReady,
weak_ptr_factory_.GetWeakPtr()));
// Register whitelists specified on the command line.
for (const auto& whitelist : GetWhitelistsFromCommandLine())
RegisterWhitelist(whitelist.first, whitelist.second, FROM_COMMAND_LINE);
}
void SupervisedUserWhitelistService::AddSiteListsChangedCallback(
const SiteListsChangedCallback& callback) {
site_lists_changed_callbacks_.push_back(callback);
std::vector<scoped_refptr<SupervisedUserSiteList>> whitelists;
GetLoadedWhitelists(&whitelists);
callback.Run(whitelists);
}
// static
std::map<std::string, std::string>
SupervisedUserWhitelistService::GetWhitelistsFromCommandLine() {
std::map<std::string, std::string> whitelists;
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
std::string command_line_whitelists = command_line->GetSwitchValueASCII(
switches::kInstallSupervisedUserWhitelists);
std::vector<base::StringPiece> string_pieces =
base::SplitStringPiece(command_line_whitelists, ",",
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
for (const base::StringPiece& whitelist : string_pieces) {
std::string id;
std::string name;
size_t separator = whitelist.find(':');
if (separator != base::StringPiece::npos) {
whitelist.substr(0, separator).CopyToString(&id);
whitelist.substr(separator + 1).CopyToString(&name);
} else {
whitelist.CopyToString(&id);
}
const bool result = whitelists.insert(std::make_pair(id, name)).second;
DCHECK(result);
}
return whitelists;
}
void SupervisedUserWhitelistService::LoadWhitelistForTesting(
const std::string& id,
const base::string16& title,
const base::FilePath& path) {
bool result = registered_whitelists_.insert(id).second;
DCHECK(result);
OnWhitelistReady(id, title, base::FilePath(), path);
}
void SupervisedUserWhitelistService::UnloadWhitelist(const std::string& id) {
bool result = registered_whitelists_.erase(id) > 0u;
DCHECK(result);
loaded_whitelists_.erase(id);
NotifyWhitelistsChanged();
}
// static
syncer::SyncData SupervisedUserWhitelistService::CreateWhitelistSyncData(
const std::string& id,
const std::string& name) {
sync_pb::EntitySpecifics specifics;
sync_pb::ManagedUserWhitelistSpecifics* whitelist =
specifics.mutable_managed_user_whitelist();
whitelist->set_id(id);
whitelist->set_name(name);
return syncer::SyncData::CreateLocalData(id, name, specifics);
}
syncer::SyncMergeResult
SupervisedUserWhitelistService::MergeDataAndStartSyncing(
syncer::ModelType type,
const syncer::SyncDataList& initial_sync_data,
std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
std::unique_ptr<syncer::SyncErrorFactory> error_handler) {
DCHECK_EQ(syncer::SUPERVISED_USER_WHITELISTS, type);
syncer::SyncChangeList change_list;
syncer::SyncMergeResult result(syncer::SUPERVISED_USER_WHITELISTS);
DictionaryPrefUpdate update(prefs_, prefs::kSupervisedUserWhitelists);
base::DictionaryValue* pref_dict = update.Get();
result.set_num_items_before_association(pref_dict->size());
std::set<std::string> seen_ids;
int num_items_added = 0;
int num_items_modified = 0;
for (const syncer::SyncData& sync_data : initial_sync_data) {
DCHECK_EQ(syncer::SUPERVISED_USER_WHITELISTS, sync_data.GetDataType());
const sync_pb::ManagedUserWhitelistSpecifics& whitelist =
sync_data.GetSpecifics().managed_user_whitelist();
std::string id = whitelist.id();
std::string name = whitelist.name();
seen_ids.insert(id);
base::DictionaryValue* dict = nullptr;
if (pref_dict->GetDictionary(id, &dict)) {
std::string old_name;
bool result = dict->GetString(kName, &old_name);
DCHECK(result);
if (name != old_name) {
SetWhitelistProperties(dict, whitelist);
num_items_modified++;
}
} else {
num_items_added++;
AddNewWhitelist(pref_dict, whitelist);
}
}
std::set<std::string> ids_to_remove;
for (base::DictionaryValue::Iterator it(*pref_dict); !it.IsAtEnd();
it.Advance()) {
if (seen_ids.find(it.key()) == seen_ids.end())
ids_to_remove.insert(it.key());
}
for (const std::string& id : ids_to_remove)
RemoveWhitelist(pref_dict, id);
// Notify if whitelists have been uninstalled. We will notify about newly
// added whitelists later, when they are actually available
// (in OnWhitelistLoaded).
if (!ids_to_remove.empty())
NotifyWhitelistsChanged();
result.set_num_items_added(num_items_added);
result.set_num_items_modified(num_items_modified);
result.set_num_items_deleted(ids_to_remove.size());
result.set_num_items_after_association(pref_dict->size());
return result;
}
void SupervisedUserWhitelistService::StopSyncing(syncer::ModelType type) {
DCHECK_EQ(syncer::SUPERVISED_USER_WHITELISTS, type);
}
syncer::SyncDataList SupervisedUserWhitelistService::GetAllSyncData(
syncer::ModelType type) const {
syncer::SyncDataList sync_data;
const base::DictionaryValue* whitelists =
prefs_->GetDictionary(prefs::kSupervisedUserWhitelists);
for (base::DictionaryValue::Iterator it(*whitelists); !it.IsAtEnd();
it.Advance()) {
const std::string& id = it.key();
const base::DictionaryValue* dict = nullptr;
it.value().GetAsDictionary(&dict);
std::string name;
bool result = dict->GetString(kName, &name);
DCHECK(result);
sync_pb::EntitySpecifics specifics;
sync_pb::ManagedUserWhitelistSpecifics* whitelist =
specifics.mutable_managed_user_whitelist();
whitelist->set_id(id);
whitelist->set_name(name);
sync_data.push_back(syncer::SyncData::CreateLocalData(id, name, specifics));
}
return sync_data;
}
syncer::SyncError SupervisedUserWhitelistService::ProcessSyncChanges(
const base::Location& from_here,
const syncer::SyncChangeList& change_list) {
bool whitelists_removed = false;
syncer::SyncError error;
DictionaryPrefUpdate update(prefs_, prefs::kSupervisedUserWhitelists);
base::DictionaryValue* pref_dict = update.Get();
for (const syncer::SyncChange& sync_change : change_list) {
syncer::SyncData data = sync_change.sync_data();
DCHECK_EQ(syncer::SUPERVISED_USER_WHITELISTS, data.GetDataType());
const sync_pb::ManagedUserWhitelistSpecifics& whitelist =
data.GetSpecifics().managed_user_whitelist();
std::string id = whitelist.id();
switch (sync_change.change_type()) {
case syncer::SyncChange::ACTION_ADD: {
DCHECK(!pref_dict->HasKey(id)) << id;
AddNewWhitelist(pref_dict, whitelist);
break;
}
case syncer::SyncChange::ACTION_UPDATE: {
base::DictionaryValue* dict = nullptr;
pref_dict->GetDictionaryWithoutPathExpansion(id, &dict);
SetWhitelistProperties(dict, whitelist);
break;
}
case syncer::SyncChange::ACTION_DELETE: {
DCHECK(pref_dict->HasKey(id)) << id;
RemoveWhitelist(pref_dict, id);
whitelists_removed = true;
break;
}
case syncer::SyncChange::ACTION_INVALID: {
NOTREACHED();
break;
}
}
}
if (whitelists_removed)
NotifyWhitelistsChanged();
return error;
}
void SupervisedUserWhitelistService::AddNewWhitelist(
base::DictionaryValue* pref_dict,
const sync_pb::ManagedUserWhitelistSpecifics& whitelist) {
base::RecordAction(base::UserMetricsAction("ManagedUsers_Whitelist_Added"));
RegisterWhitelist(whitelist.id(), whitelist.name(), FROM_SYNC);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
SetWhitelistProperties(dict.get(), whitelist);
pref_dict->SetWithoutPathExpansion(whitelist.id(), std::move(dict));
}
void SupervisedUserWhitelistService::SetWhitelistProperties(
base::DictionaryValue* dict,
const sync_pb::ManagedUserWhitelistSpecifics& whitelist) {
dict->SetString(kName, whitelist.name());
}
void SupervisedUserWhitelistService::RemoveWhitelist(
base::DictionaryValue* pref_dict,
const std::string& id) {
base::RecordAction(base::UserMetricsAction("ManagedUsers_Whitelist_Removed"));
pref_dict->RemoveWithoutPathExpansion(id, NULL);
installer_->UnregisterWhitelist(client_id_, id);
UnloadWhitelist(id);
}
void SupervisedUserWhitelistService::RegisterWhitelist(const std::string& id,
const std::string& name,
WhitelistSource source) {
bool result = registered_whitelists_.insert(id).second;
DCHECK(result);
// Using an empty client ID for whitelists installed from the command line
// causes the installer to not persist the installation, so the whitelist will
// be removed the next time the browser is started without the command line
// flag.
installer_->RegisterWhitelist(
source == FROM_COMMAND_LINE ? std::string() : client_id_, id, name);
}
void SupervisedUserWhitelistService::GetLoadedWhitelists(
std::vector<scoped_refptr<SupervisedUserSiteList>>* whitelists) {
for (const auto& whitelist : loaded_whitelists_)
whitelists->push_back(whitelist.second);
}
void SupervisedUserWhitelistService::NotifyWhitelistsChanged() {
std::vector<scoped_refptr<SupervisedUserSiteList>> whitelists;
GetLoadedWhitelists(&whitelists);
for (const auto& callback : site_lists_changed_callbacks_)
callback.Run(whitelists);
}
void SupervisedUserWhitelistService::OnWhitelistReady(
const std::string& id,
const base::string16& title,
const base::FilePath& large_icon_path,
const base::FilePath& whitelist_path) {
// If we did not register the whitelist or it has been unregistered in the
// mean time, ignore it.
if (registered_whitelists_.count(id) == 0u)
return;
SupervisedUserSiteList::Load(
id, title, large_icon_path, whitelist_path,
base::Bind(&SupervisedUserWhitelistService::OnWhitelistLoaded,
weak_ptr_factory_.GetWeakPtr(), id, base::TimeTicks::Now()));
}
void SupervisedUserWhitelistService::OnWhitelistLoaded(
const std::string& id,
base::TimeTicks start_time,
const scoped_refptr<SupervisedUserSiteList>& whitelist) {
if (!whitelist) {
LOG(WARNING) << "Couldn't load whitelist " << id;
return;
}
UMA_HISTOGRAM_TIMES("ManagedUsers.Whitelist.TotalLoadDuration",
base::TimeTicks::Now() - start_time);
// If the whitelist has been unregistered in the mean time, ignore it.
if (registered_whitelists_.count(id) == 0u)
return;
loaded_whitelists_[id] = whitelist;
NotifyWhitelistsChanged();
}