blob: 042654ad3834f47998bed1da9fa9ed27591acc05 [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_shared_settings_service.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/values.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "sync/api/sync_change.h"
#include "sync/api/sync_data.h"
#include "sync/api/sync_error.h"
#include "sync/api/sync_error_factory.h"
#include "sync/api/sync_merge_result.h"
#include "sync/protocol/sync.pb.h"
using base::DictionaryValue;
using base::Value;
using syncer::SUPERVISED_USER_SHARED_SETTINGS;
using syncer::ModelType;
using syncer::SyncChange;
using syncer::SyncChangeList;
using syncer::SyncChangeProcessor;
using syncer::SyncData;
using syncer::SyncDataList;
using syncer::SyncError;
using syncer::SyncErrorFactory;
using syncer::SyncMergeResult;
namespace {
const char kAcknowledged[] = "acknowledged";
const char kValue[] = "value";
DictionaryValue* FindOrCreateDictionary(DictionaryValue* parent,
const std::string& key) {
DictionaryValue* dict = NULL;
if (!parent->GetDictionaryWithoutPathExpansion(key, &dict)) {
dict = new DictionaryValue;
parent->SetWithoutPathExpansion(key, dict);
}
return dict;
}
class ScopedSupervisedUserSharedSettingsUpdate {
public:
ScopedSupervisedUserSharedSettingsUpdate(PrefService* prefs,
const std::string& su_id)
: update_(prefs, prefs::kSupervisedUserSharedSettings), su_id_(su_id) {
DCHECK(!su_id.empty());
// A supervised user can only modify their own settings.
std::string id = prefs->GetString(prefs::kSupervisedUserId);
DCHECK(id.empty() || id == su_id);
}
DictionaryValue* Get() {
return FindOrCreateDictionary(update_.Get(), su_id_);
}
private:
DictionaryPrefUpdate update_;
std::string su_id_;
};
SyncData CreateSyncDataForValue(
const std::string& su_id,
const std::string& key,
const Value& dict_value) {
const DictionaryValue* dict = NULL;
if (!dict_value.GetAsDictionary(&dict))
return SyncData();
const Value* value = NULL;
if (!dict->Get(kValue, &value))
return SyncData();
bool acknowledged = false;
dict->GetBoolean(kAcknowledged, &acknowledged);
return SupervisedUserSharedSettingsService::CreateSyncDataForSetting(
su_id, key, *value, acknowledged);
}
} // namespace
SupervisedUserSharedSettingsService::SupervisedUserSharedSettingsService(
PrefService* prefs)
: prefs_(prefs) {}
SupervisedUserSharedSettingsService::~SupervisedUserSharedSettingsService() {}
void SupervisedUserSharedSettingsService::SetValueInternal(
const std::string& su_id,
const std::string& key,
const Value& value,
bool acknowledged) {
ScopedSupervisedUserSharedSettingsUpdate update(prefs_, su_id);
DictionaryValue* update_dict = update.Get();
DictionaryValue* dict = NULL;
bool has_key = update_dict->GetDictionaryWithoutPathExpansion(key, &dict);
if (!has_key) {
dict = new DictionaryValue;
update_dict->SetWithoutPathExpansion(key, dict);
}
dict->SetWithoutPathExpansion(kValue, value.DeepCopy());
dict->SetBooleanWithoutPathExpansion(kAcknowledged, acknowledged);
if (!sync_processor_)
return;
SyncData data = CreateSyncDataForSetting(su_id, key, value, acknowledged);
SyncChange::SyncChangeType change_type =
has_key ? SyncChange::ACTION_UPDATE : SyncChange::ACTION_ADD;
SyncChangeList changes;
changes.push_back(SyncChange(FROM_HERE, change_type, data));
SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
DCHECK(!error.IsSet()) << error.ToString();
}
const Value* SupervisedUserSharedSettingsService::GetValue(
const std::string& su_id,
const std::string& key) {
const DictionaryValue* data =
prefs_->GetDictionary(prefs::kSupervisedUserSharedSettings);
const DictionaryValue* dict = NULL;
if (!data->GetDictionaryWithoutPathExpansion(su_id, &dict))
return NULL;
const DictionaryValue* settings = NULL;
if (!dict->GetDictionaryWithoutPathExpansion(key, &settings))
return NULL;
const Value* value = NULL;
if (!settings->GetWithoutPathExpansion(kValue, &value))
return NULL;
return value;
}
void SupervisedUserSharedSettingsService::SetValue(
const std::string& su_id,
const std::string& key,
const Value& value) {
SetValueInternal(su_id, key, value, true);
}
scoped_ptr<
SupervisedUserSharedSettingsService::ChangeCallbackList::Subscription>
SupervisedUserSharedSettingsService::Subscribe(
const SupervisedUserSharedSettingsService::ChangeCallback& cb) {
return callbacks_.Add(cb);
}
// static
void SupervisedUserSharedSettingsService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(
prefs::kSupervisedUserSharedSettings,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
// static
SyncData SupervisedUserSharedSettingsService::CreateSyncDataForSetting(
const std::string& su_id,
const std::string& key,
const Value& value,
bool acknowledged) {
std::string json_value;
base::JSONWriter::Write(&value, &json_value);
::sync_pb::EntitySpecifics specifics;
specifics.mutable_managed_user_shared_setting()->set_mu_id(su_id);
specifics.mutable_managed_user_shared_setting()->set_key(key);
specifics.mutable_managed_user_shared_setting()->set_value(json_value);
specifics.mutable_managed_user_shared_setting()->set_acknowledged(
acknowledged);
std::string title = su_id + ":" + key;
return SyncData::CreateLocalData(title, title, specifics);
}
void SupervisedUserSharedSettingsService::Shutdown() {}
syncer::SyncMergeResult
SupervisedUserSharedSettingsService::MergeDataAndStartSyncing(
syncer::ModelType type,
const syncer::SyncDataList& initial_sync_data,
scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
scoped_ptr<syncer::SyncErrorFactory> error_handler) {
DCHECK_EQ(SUPERVISED_USER_SHARED_SETTINGS, type);
sync_processor_ = sync_processor.Pass();
error_handler_ = error_handler.Pass();
// We keep a map from MU ID to the set of keys that we have seen in the
// initial sync data.
std::map<std::string, std::set<std::string> > seen_keys;
// Iterate over all initial sync data, and update it locally. This means that
// the value from the server always wins over a local value.
for (SyncDataList::const_iterator it = initial_sync_data.begin();
it != initial_sync_data.end();
++it) {
DCHECK_EQ(SUPERVISED_USER_SHARED_SETTINGS, it->GetDataType());
const ::sync_pb::ManagedUserSharedSettingSpecifics&
supervised_user_shared_setting =
it->GetSpecifics().managed_user_shared_setting();
scoped_ptr<Value> value(
base::JSONReader::Read(supervised_user_shared_setting.value()));
const std::string& su_id = supervised_user_shared_setting.mu_id();
ScopedSupervisedUserSharedSettingsUpdate update(prefs_, su_id);
const std::string& key = supervised_user_shared_setting.key();
DictionaryValue* dict = FindOrCreateDictionary(update.Get(), key);
dict->SetWithoutPathExpansion(kValue, value.release());
// Every setting we get from the server should have the acknowledged flag
// set.
DCHECK(supervised_user_shared_setting.acknowledged());
dict->SetBooleanWithoutPathExpansion(
kAcknowledged, supervised_user_shared_setting.acknowledged());
callbacks_.Notify(su_id, key);
seen_keys[su_id].insert(key);
}
// Iterate over all settings that we have locally, which includes settings
// that were just synced down. We filter those out using |seen_keys|.
SyncChangeList change_list;
const DictionaryValue* all_settings =
prefs_->GetDictionary(prefs::kSupervisedUserSharedSettings);
for (DictionaryValue::Iterator it(*all_settings); !it.IsAtEnd();
it.Advance()) {
const DictionaryValue* dict = NULL;
bool success = it.value().GetAsDictionary(&dict);
DCHECK(success);
const std::set<std::string>& seen = seen_keys[it.key()];
for (DictionaryValue::Iterator jt(*dict); !jt.IsAtEnd(); jt.Advance()) {
// We only need to upload settings that we haven't seen in the initial
// sync data (which means they were added locally).
if (seen.count(jt.key()) > 0)
continue;
SyncData data = CreateSyncDataForValue(it.key(), jt.key(), jt.value());
DCHECK(data.IsValid());
change_list.push_back(
SyncChange(FROM_HERE, SyncChange::ACTION_ADD, data));
}
}
SyncMergeResult result(SUPERVISED_USER_SHARED_SETTINGS);
// Process all the accumulated changes.
if (change_list.size() > 0) {
result.set_error(
sync_processor_->ProcessSyncChanges(FROM_HERE, change_list));
}
// TODO(bauerb): Statistics?
return result;
}
void SupervisedUserSharedSettingsService::StopSyncing(syncer::ModelType type) {
DCHECK_EQ(SUPERVISED_USER_SHARED_SETTINGS, type);
sync_processor_.reset();
error_handler_.reset();
}
syncer::SyncDataList SupervisedUserSharedSettingsService::GetAllSyncData(
syncer::ModelType type) const {
DCHECK_EQ(SUPERVISED_USER_SHARED_SETTINGS, type);
SyncDataList data;
const DictionaryValue* all_settings =
prefs_->GetDictionary(prefs::kSupervisedUserSharedSettings);
for (DictionaryValue::Iterator it(*all_settings); !it.IsAtEnd();
it.Advance()) {
const DictionaryValue* dict = NULL;
bool success = it.value().GetAsDictionary(&dict);
DCHECK(success);
for (DictionaryValue::Iterator jt(*dict); !jt.IsAtEnd(); jt.Advance()) {
data.push_back(CreateSyncDataForValue(it.key(), jt.key(), jt.value()));
}
}
return data;
}
syncer::SyncError SupervisedUserSharedSettingsService::ProcessSyncChanges(
const tracked_objects::Location& from_here,
const syncer::SyncChangeList& change_list) {
for (SyncChangeList::const_iterator it = change_list.begin();
it != change_list.end();
++it) {
SyncData data = it->sync_data();
DCHECK_EQ(SUPERVISED_USER_SHARED_SETTINGS, data.GetDataType());
const ::sync_pb::ManagedUserSharedSettingSpecifics&
supervised_user_shared_setting =
data.GetSpecifics().managed_user_shared_setting();
const std::string& key = supervised_user_shared_setting.key();
const std::string& su_id = supervised_user_shared_setting.mu_id();
ScopedSupervisedUserSharedSettingsUpdate update(prefs_, su_id);
DictionaryValue* update_dict = update.Get();
DictionaryValue* dict = NULL;
bool has_key = update_dict->GetDictionaryWithoutPathExpansion(key, &dict);
switch (it->change_type()) {
case SyncChange::ACTION_ADD:
case SyncChange::ACTION_UPDATE: {
// Every setting we get from the server should have the acknowledged
// flag set.
DCHECK(supervised_user_shared_setting.acknowledged());
if (has_key) {
// If the supervised user already exists, it should be an update
// action.
DCHECK_EQ(SyncChange::ACTION_UPDATE, it->change_type());
} else {
// Otherwise, it should be an add action.
DCHECK_EQ(SyncChange::ACTION_ADD, it->change_type());
dict = new DictionaryValue;
update_dict->SetWithoutPathExpansion(key, dict);
}
scoped_ptr<Value> value(
base::JSONReader::Read(supervised_user_shared_setting.value()));
dict->SetWithoutPathExpansion(kValue, value.release());
dict->SetBooleanWithoutPathExpansion(
kAcknowledged, supervised_user_shared_setting.acknowledged());
break;
}
case SyncChange::ACTION_DELETE: {
if (has_key)
update_dict->RemoveWithoutPathExpansion(key, NULL);
else
NOTREACHED() << "Trying to delete nonexistent key " << key;
break;
}
case SyncChange::ACTION_INVALID: {
NOTREACHED();
break;
}
}
callbacks_.Notify(su_id, key);
}
SyncError error;
return error;
}