| // 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(provider::ConfigStore* config_store) { | 
 |   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(); | 
 |  | 
 |   if (!config_store) | 
 |     return result; | 
 |  | 
 |   // Crash on any mistakes in defaults. | 
 |   CHECK(config_store->LoadDefaults(&result)); | 
 |  | 
 |   CHECK(!result.client_id.empty()); | 
 |   CHECK(!result.client_secret.empty()); | 
 |   CHECK(!result.api_key.empty()); | 
 |   CHECK(!result.oauth_url.empty()); | 
 |   CHECK(!result.service_url.empty()); | 
 |   CHECK(!result.xmpp_endpoint.empty()); | 
 |   CHECK(!result.oem_name.empty()); | 
 |   CHECK(!result.model_name.empty()); | 
 |   CHECK(!result.model_id.empty()); | 
 |   CHECK(!result.name.empty()); | 
 |   CHECK(!result.device_id.empty()); | 
 |   CHECK_EQ(result.embedded_code.empty(), | 
 |            (result.pairing_modes.find(PairingType::kEmbeddedCode) == | 
 |             result.pairing_modes.end())); | 
 |  | 
 |   // Values below will be generated at runtime. | 
 |   CHECK(result.cloud_id.empty()); | 
 |   CHECK(result.refresh_token.empty()); | 
 |   CHECK(result.robot_account.empty()); | 
 |   CHECK(result.last_configured_ssid.empty()); | 
 |   CHECK(result.secret.empty()); | 
 |   CHECK(result.root_client_token_owner == RootClientTokenOwner::kNone); | 
 |  | 
 |   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) | 
 |     : defaults_{CreateDefaultSettings(config_store)}, | 
 |       settings_{defaults_}, | 
 |       config_store_{config_store} { | 
 |   Transaction change{this}; | 
 |   change.save_ = false; | 
 |   change.LoadState(); | 
 | } | 
 |  | 
 | 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_; | 
 | } | 
 |  | 
 | const Config::Settings& Config::GetDefaults() const { | 
 |   return defaults_; | 
 | } | 
 |  | 
 | 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 |