// Copyright 2015 The Weave 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 "src/config.h"
#include <set>
#include <base/bind.h>
#include <base/guid.h>
#include <base/json/json_reader.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/values.h>
#include <weave/enum_to_string.h>
#include "src/data_encoding.h"
#include "src/privet/privet_types.h"
#include "src/string_utils.h"
#include "src/bind_lambda.h"
namespace weave {
const char kConfigName[] = "config";
namespace config_keys {
const char kVersion[] = "version";
const char kClientId[] = "client_id";
const char kClientSecret[] = "client_secret";
const char kApiKey[] = "api_key";
const char kOAuthURL[] = "oauth_url";
const char kServiceURL[] = "service_url";
const char kXmppEndpoint[] = "xmpp_endpoint";
const char kName[] = "name";
const char kDescription[] = "description";
const char kLocation[] = "location";
const char kLocalAnonymousAccessRole[] = "local_anonymous_access_role";
const char kLocalDiscoveryEnabled[] = "local_discovery_enabled";
const char kLocalPairingEnabled[] = "local_pairing_enabled";
const char kRefreshToken[] = "refresh_token";
const char kCloudId[] = "cloud_id";
const char kDeviceId[] = "device_id";
const char kRobotAccount[] = "robot_account";
const char kLastConfiguredSsid[] = "last_configured_ssid";
const char kSecret[] = "secret";
const char kRootClientTokenOwner[] = "root_client_token_owner";
} // namespace config_keys
const char kWeaveUrl[] = "https://www.googleapis.com/weave/v1/";
const char kDeprecatedUrl[] = "https://www.googleapis.com/clouddevices/v1/";
const char kXmppEndpoint[] = "talk.google.com:5223";
namespace {
const int kCurrentConfigVersion = 1;
void MigrateFromV0(base::DictionaryValue* dict) {
std::string cloud_id;
if (dict->GetString(config_keys::kCloudId, &cloud_id) && !cloud_id.empty())
return;
scoped_ptr<base::Value> tmp;
if (dict->Remove(config_keys::kDeviceId, &tmp))
dict->Set(config_keys::kCloudId, std::move(tmp));
}
Config::Settings CreateDefaultSettings() {
Config::Settings result;
result.oauth_url = "https://accounts.google.com/o/oauth2/";
result.service_url = kWeaveUrl;
result.xmpp_endpoint = kXmppEndpoint;
result.local_anonymous_access_role = AuthScope::kViewer;
result.pairing_modes.insert(PairingType::kPinCode);
result.device_id = base::GenerateGUID();
return result;
}
const EnumToStringMap<RootClientTokenOwner>::Map kRootClientTokenOwnerMap[] = {
{RootClientTokenOwner::kNone, "none"},
{RootClientTokenOwner::kClient, "client"},
{RootClientTokenOwner::kCloud, "cloud"},
};
} // namespace
template <>
LIBWEAVE_EXPORT EnumToStringMap<RootClientTokenOwner>::EnumToStringMap()
: EnumToStringMap(kRootClientTokenOwnerMap) {}
Config::Config(provider::ConfigStore* config_store)
: settings_{CreateDefaultSettings()}, config_store_{config_store} {
Load();
}
void Config::AddOnChangedCallback(const OnChangedCallback& callback) {
on_changed_.push_back(callback);
// Force to read current state.
callback.Run(settings_);
}
const Config::Settings& Config::GetSettings() const {
return settings_;
}
void Config::Load() {
Transaction change{this};
change.save_ = false;
settings_ = CreateDefaultSettings();
if (!config_store_)
return;
// Crash on any mistakes in defaults.
CHECK(config_store_->LoadDefaults(&settings_));
CHECK(!settings_.client_id.empty());
CHECK(!settings_.client_secret.empty());
CHECK(!settings_.api_key.empty());
CHECK(!settings_.oauth_url.empty());
CHECK(!settings_.service_url.empty());
CHECK(!settings_.xmpp_endpoint.empty());
CHECK(!settings_.oem_name.empty());
CHECK(!settings_.model_name.empty());
CHECK(!settings_.model_id.empty());
CHECK(!settings_.name.empty());
CHECK(!settings_.device_id.empty());
CHECK_EQ(settings_.embedded_code.empty(),
(settings_.pairing_modes.find(PairingType::kEmbeddedCode) ==
settings_.pairing_modes.end()));
// Values below will be generated at runtime.
CHECK(settings_.cloud_id.empty());
CHECK(settings_.refresh_token.empty());
CHECK(settings_.robot_account.empty());
CHECK(settings_.last_configured_ssid.empty());
CHECK(settings_.secret.empty());
CHECK(settings_.root_client_token_owner == RootClientTokenOwner::kNone);
change.LoadState();
}
void Config::Transaction::LoadState() {
if (!config_->config_store_)
return;
std::string json_string = config_->config_store_->LoadSettings(kConfigName);
if (json_string.empty()) {
json_string = config_->config_store_->LoadSettings();
if (json_string.empty())
return;
}
auto value = base::JSONReader::Read(json_string);
base::DictionaryValue* dict = nullptr;
if (!value || !value->GetAsDictionary(&dict)) {
LOG(ERROR) << "Failed to parse settings.";
return;
}
int loaded_version = 0;
dict->GetInteger(config_keys::kVersion, &loaded_version);
if (loaded_version != kCurrentConfigVersion) {
LOG(INFO) << "State version mismatch. expected: " << kCurrentConfigVersion
<< ", loaded: " << loaded_version;
save_ = true;
}
if (loaded_version == 0) {
MigrateFromV0(dict);
}
std::string tmp;
bool tmp_bool{false};
if (dict->GetString(config_keys::kClientId, &tmp))
set_client_id(tmp);
if (dict->GetString(config_keys::kClientSecret, &tmp))
set_client_secret(tmp);
if (dict->GetString(config_keys::kApiKey, &tmp))
set_api_key(tmp);
if (dict->GetString(config_keys::kOAuthURL, &tmp))
set_oauth_url(tmp);
if (dict->GetString(config_keys::kServiceURL, &tmp)) {
if (tmp == kDeprecatedUrl)
tmp = kWeaveUrl;
set_service_url(tmp);
}
if (dict->GetString(config_keys::kXmppEndpoint, &tmp)) {
set_xmpp_endpoint(tmp);
}
if (dict->GetString(config_keys::kName, &tmp))
set_name(tmp);
if (dict->GetString(config_keys::kDescription, &tmp))
set_description(tmp);
if (dict->GetString(config_keys::kLocation, &tmp))
set_location(tmp);
AuthScope scope{AuthScope::kNone};
if (dict->GetString(config_keys::kLocalAnonymousAccessRole, &tmp) &&
StringToEnum(tmp, &scope)) {
set_local_anonymous_access_role(scope);
}
if (dict->GetBoolean(config_keys::kLocalDiscoveryEnabled, &tmp_bool))
set_local_discovery_enabled(tmp_bool);
if (dict->GetBoolean(config_keys::kLocalPairingEnabled, &tmp_bool))
set_local_pairing_enabled(tmp_bool);
if (dict->GetString(config_keys::kCloudId, &tmp))
set_cloud_id(tmp);
if (dict->GetString(config_keys::kDeviceId, &tmp))
set_device_id(tmp);
if (dict->GetString(config_keys::kRefreshToken, &tmp))
set_refresh_token(tmp);
if (dict->GetString(config_keys::kRobotAccount, &tmp))
set_robot_account(tmp);
if (dict->GetString(config_keys::kLastConfiguredSsid, &tmp))
set_last_configured_ssid(tmp);
std::vector<uint8_t> secret;
if (dict->GetString(config_keys::kSecret, &tmp) && Base64Decode(tmp, &secret))
set_secret(secret);
RootClientTokenOwner token_owner{RootClientTokenOwner::kNone};
if (dict->GetString(config_keys::kRootClientTokenOwner, &tmp) &&
StringToEnum(tmp, &token_owner)) {
set_root_client_token_owner(token_owner);
}
}
void Config::Save() {
if (!config_store_)
return;
base::DictionaryValue dict;
dict.SetInteger(config_keys::kVersion, kCurrentConfigVersion);
dict.SetString(config_keys::kClientId, settings_.client_id);
dict.SetString(config_keys::kClientSecret, settings_.client_secret);
dict.SetString(config_keys::kApiKey, settings_.api_key);
dict.SetString(config_keys::kOAuthURL, settings_.oauth_url);
dict.SetString(config_keys::kServiceURL, settings_.service_url);
dict.SetString(config_keys::kXmppEndpoint, settings_.xmpp_endpoint);
dict.SetString(config_keys::kRefreshToken, settings_.refresh_token);
dict.SetString(config_keys::kCloudId, settings_.cloud_id);
dict.SetString(config_keys::kDeviceId, settings_.device_id);
dict.SetString(config_keys::kRobotAccount, settings_.robot_account);
dict.SetString(config_keys::kLastConfiguredSsid,
settings_.last_configured_ssid);
dict.SetString(config_keys::kSecret, Base64Encode(settings_.secret));
dict.SetString(config_keys::kRootClientTokenOwner,
EnumToString(settings_.root_client_token_owner));
dict.SetString(config_keys::kName, settings_.name);
dict.SetString(config_keys::kDescription, settings_.description);
dict.SetString(config_keys::kLocation, settings_.location);
dict.SetString(config_keys::kLocalAnonymousAccessRole,
EnumToString(settings_.local_anonymous_access_role));
dict.SetBoolean(config_keys::kLocalDiscoveryEnabled,
settings_.local_discovery_enabled);
dict.SetBoolean(config_keys::kLocalPairingEnabled,
settings_.local_pairing_enabled);
std::string json_string;
base::JSONWriter::WriteWithOptions(
dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
config_store_->SaveSettings(
kConfigName, json_string,
base::Bind([](ErrorPtr error) { CHECK(!error); }));
}
Config::Transaction::~Transaction() {
Commit();
}
void Config::Transaction::Commit() {
if (!config_)
return;
if (save_)
config_->Save();
for (const auto& cb : config_->on_changed_)
cb.Run(*settings_);
config_ = nullptr;
}
} // namespace weave