1// Copyright 2015 The Weave 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 "src/config.h"
6
7#include <set>
8
9#include <base/bind.h>
10#include <base/guid.h>
11#include <base/json/json_reader.h>
12#include <base/json/json_writer.h>
13#include <base/logging.h>
14#include <base/strings/string_number_conversions.h>
15#include <base/values.h>
16#include <weave/enum_to_string.h>
17
18#include "src/data_encoding.h"
19#include "src/privet/privet_types.h"
20#include "src/string_utils.h"
21#include "src/bind_lambda.h"
22
23namespace weave {
24
25const char kConfigName[] = "config";
26
27namespace config_keys {
28
29const char kVersion[] = "version";
30
31const char kClientId[] = "client_id";
32const char kClientSecret[] = "client_secret";
33const char kApiKey[] = "api_key";
34const char kOAuthURL[] = "oauth_url";
35const char kServiceURL[] = "service_url";
36const char kXmppEndpoint[] = "xmpp_endpoint";
37const char kName[] = "name";
38const char kDescription[] = "description";
39const char kLocation[] = "location";
40const char kLocalAnonymousAccessRole[] = "local_anonymous_access_role";
41const char kLocalDiscoveryEnabled[] = "local_discovery_enabled";
42const char kLocalPairingEnabled[] = "local_pairing_enabled";
43const char kRefreshToken[] = "refresh_token";
44const char kCloudId[] = "cloud_id";
45const char kDeviceId[] = "device_id";
46const char kRobotAccount[] = "robot_account";
47const char kLastConfiguredSsid[] = "last_configured_ssid";
48const char kSecret[] = "secret";
49const char kRootClientTokenOwner[] = "root_client_token_owner";
50
51}  // namespace config_keys
52
53const char kWeaveUrl[] = "https://www.googleapis.com/weave/v1/";
54const char kDeprecatedUrl[] = "https://www.googleapis.com/clouddevices/v1/";
55const char kXmppEndpoint[] = "talk.google.com:5223";
56
57namespace {
58
59const int kCurrentConfigVersion = 1;
60
61void MigrateFromV0(base::DictionaryValue* dict) {
62  std::string cloud_id;
63  if (dict->GetString(config_keys::kCloudId, &cloud_id) && !cloud_id.empty())
64    return;
65  scoped_ptr<base::Value> tmp;
66  if (dict->Remove(config_keys::kDeviceId, &tmp))
67    dict->Set(config_keys::kCloudId, std::move(tmp));
68}
69
70Config::Settings CreateDefaultSettings() {
71  Config::Settings result;
72  result.oauth_url = "https://accounts.google.com/o/oauth2/";
73  result.service_url = kWeaveUrl;
74  result.xmpp_endpoint = kXmppEndpoint;
75  result.local_anonymous_access_role = AuthScope::kViewer;
76  result.pairing_modes.insert(PairingType::kPinCode);
77  result.device_id = base::GenerateGUID();
78  return result;
79}
80
81const EnumToStringMap<RootClientTokenOwner>::Map kRootClientTokenOwnerMap[] = {
82    {RootClientTokenOwner::kNone, "none"},
83    {RootClientTokenOwner::kClient, "client"},
84    {RootClientTokenOwner::kCloud, "cloud"},
85};
86
87}  // namespace
88
89template <>
90LIBWEAVE_EXPORT EnumToStringMap<RootClientTokenOwner>::EnumToStringMap()
91    : EnumToStringMap(kRootClientTokenOwnerMap) {}
92
93Config::Config(provider::ConfigStore* config_store)
94    : settings_{CreateDefaultSettings()}, config_store_{config_store} {
95  Load();
96}
97
98void Config::AddOnChangedCallback(const OnChangedCallback& callback) {
99  on_changed_.push_back(callback);
100  // Force to read current state.
101  callback.Run(settings_);
102}
103
104const Config::Settings& Config::GetSettings() const {
105  return settings_;
106}
107
108void Config::Load() {
109  Transaction change{this};
110  change.save_ = false;
111
112  settings_ = CreateDefaultSettings();
113
114  if (!config_store_)
115    return;
116
117  // Crash on any mistakes in defaults.
118  CHECK(config_store_->LoadDefaults(&settings_));
119
120  CHECK(!settings_.client_id.empty());
121  CHECK(!settings_.client_secret.empty());
122  CHECK(!settings_.api_key.empty());
123  CHECK(!settings_.oauth_url.empty());
124  CHECK(!settings_.service_url.empty());
125  CHECK(!settings_.xmpp_endpoint.empty());
126  CHECK(!settings_.oem_name.empty());
127  CHECK(!settings_.model_name.empty());
128  CHECK(!settings_.model_id.empty());
129  CHECK(!settings_.name.empty());
130  CHECK(!settings_.device_id.empty());
131  CHECK_EQ(settings_.embedded_code.empty(),
132           (settings_.pairing_modes.find(PairingType::kEmbeddedCode) ==
133              settings_.pairing_modes.end()));
134
135  // Values below will be generated at runtime.
136  CHECK(settings_.cloud_id.empty());
137  CHECK(settings_.refresh_token.empty());
138  CHECK(settings_.robot_account.empty());
139  CHECK(settings_.last_configured_ssid.empty());
140  CHECK(settings_.secret.empty());
141  CHECK(settings_.root_client_token_owner == RootClientTokenOwner::kNone);
142
143  change.LoadState();
144}
145
146void Config::Transaction::LoadState() {
147  if (!config_->config_store_)
148    return;
149  std::string json_string = config_->config_store_->LoadSettings(kConfigName);
150  if (json_string.empty()) {
151    json_string = config_->config_store_->LoadSettings();
152    if (json_string.empty())
153      return;
154  }
155
156  auto value = base::JSONReader::Read(json_string);
157  base::DictionaryValue* dict = nullptr;
158  if (!value || !value->GetAsDictionary(&dict)) {
159    LOG(ERROR) << "Failed to parse settings.";
160    return;
161  }
162
163  int loaded_version = 0;
164  dict->GetInteger(config_keys::kVersion, &loaded_version);
165
166  if (loaded_version != kCurrentConfigVersion) {
167    LOG(INFO) << "State version mismatch. expected: " << kCurrentConfigVersion
168              << ", loaded: " << loaded_version;
169    save_ = true;
170  }
171
172  if (loaded_version == 0) {
173    MigrateFromV0(dict);
174  }
175
176  std::string tmp;
177  bool tmp_bool{false};
178
179  if (dict->GetString(config_keys::kClientId, &tmp))
180    set_client_id(tmp);
181
182  if (dict->GetString(config_keys::kClientSecret, &tmp))
183    set_client_secret(tmp);
184
185  if (dict->GetString(config_keys::kApiKey, &tmp))
186    set_api_key(tmp);
187
188  if (dict->GetString(config_keys::kOAuthURL, &tmp))
189    set_oauth_url(tmp);
190
191  if (dict->GetString(config_keys::kServiceURL, &tmp)) {
192    if (tmp == kDeprecatedUrl)
193      tmp = kWeaveUrl;
194    set_service_url(tmp);
195  }
196
197  if (dict->GetString(config_keys::kXmppEndpoint, &tmp)) {
198    set_xmpp_endpoint(tmp);
199  }
200
201  if (dict->GetString(config_keys::kName, &tmp))
202    set_name(tmp);
203
204  if (dict->GetString(config_keys::kDescription, &tmp))
205    set_description(tmp);
206
207  if (dict->GetString(config_keys::kLocation, &tmp))
208    set_location(tmp);
209
210  AuthScope scope{AuthScope::kNone};
211  if (dict->GetString(config_keys::kLocalAnonymousAccessRole, &tmp) &&
212      StringToEnum(tmp, &scope)) {
213    set_local_anonymous_access_role(scope);
214  }
215
216  if (dict->GetBoolean(config_keys::kLocalDiscoveryEnabled, &tmp_bool))
217    set_local_discovery_enabled(tmp_bool);
218
219  if (dict->GetBoolean(config_keys::kLocalPairingEnabled, &tmp_bool))
220    set_local_pairing_enabled(tmp_bool);
221
222  if (dict->GetString(config_keys::kCloudId, &tmp))
223    set_cloud_id(tmp);
224
225  if (dict->GetString(config_keys::kDeviceId, &tmp))
226    set_device_id(tmp);
227
228  if (dict->GetString(config_keys::kRefreshToken, &tmp))
229    set_refresh_token(tmp);
230
231  if (dict->GetString(config_keys::kRobotAccount, &tmp))
232    set_robot_account(tmp);
233
234  if (dict->GetString(config_keys::kLastConfiguredSsid, &tmp))
235    set_last_configured_ssid(tmp);
236
237  std::vector<uint8_t> secret;
238  if (dict->GetString(config_keys::kSecret, &tmp) && Base64Decode(tmp, &secret))
239    set_secret(secret);
240
241  RootClientTokenOwner token_owner{RootClientTokenOwner::kNone};
242  if (dict->GetString(config_keys::kRootClientTokenOwner, &tmp) &&
243      StringToEnum(tmp, &token_owner)) {
244    set_root_client_token_owner(token_owner);
245  }
246}
247
248void Config::Save() {
249  if (!config_store_)
250    return;
251
252  base::DictionaryValue dict;
253  dict.SetInteger(config_keys::kVersion, kCurrentConfigVersion);
254
255  dict.SetString(config_keys::kClientId, settings_.client_id);
256  dict.SetString(config_keys::kClientSecret, settings_.client_secret);
257  dict.SetString(config_keys::kApiKey, settings_.api_key);
258  dict.SetString(config_keys::kOAuthURL, settings_.oauth_url);
259  dict.SetString(config_keys::kServiceURL, settings_.service_url);
260  dict.SetString(config_keys::kXmppEndpoint, settings_.xmpp_endpoint);
261  dict.SetString(config_keys::kRefreshToken, settings_.refresh_token);
262  dict.SetString(config_keys::kCloudId, settings_.cloud_id);
263  dict.SetString(config_keys::kDeviceId, settings_.device_id);
264  dict.SetString(config_keys::kRobotAccount, settings_.robot_account);
265  dict.SetString(config_keys::kLastConfiguredSsid,
266                 settings_.last_configured_ssid);
267  dict.SetString(config_keys::kSecret, Base64Encode(settings_.secret));
268  dict.SetString(config_keys::kRootClientTokenOwner,
269                 EnumToString(settings_.root_client_token_owner));
270  dict.SetString(config_keys::kName, settings_.name);
271  dict.SetString(config_keys::kDescription, settings_.description);
272  dict.SetString(config_keys::kLocation, settings_.location);
273  dict.SetString(config_keys::kLocalAnonymousAccessRole,
274                 EnumToString(settings_.local_anonymous_access_role));
275  dict.SetBoolean(config_keys::kLocalDiscoveryEnabled,
276                  settings_.local_discovery_enabled);
277  dict.SetBoolean(config_keys::kLocalPairingEnabled,
278                  settings_.local_pairing_enabled);
279
280  std::string json_string;
281  base::JSONWriter::WriteWithOptions(
282      dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
283
284  config_store_->SaveSettings(
285      kConfigName, json_string,
286      base::Bind([](ErrorPtr error) { CHECK(!error); }));
287}
288
289Config::Transaction::~Transaction() {
290  Commit();
291}
292
293void Config::Transaction::Commit() {
294  if (!config_)
295    return;
296  if (save_)
297    config_->Save();
298  for (const auto& cb : config_->on_changed_)
299    cb.Run(*settings_);
300  config_ = nullptr;
301}
302
303}  // namespace weave
304