Merge remote-tracking branch 'weave/master' into 'weave/aosp-master'
The following changes are included in this merge:
47743a3 Fixed name of the function to match design docs.
aef87f5 Oven virtual device sample initial commit. Used as weave device for CES demo.
b3de351 Fix device registration expiration with system clock adjusted
532140d Add 'timeMs' field to /privet/info local API
6fdca1f component_manager: fix uint comparison in unittest
1884cf6 Rename timeMs into time
b03d4dd Remove /privet/info:uptime as unused
30ba607 privet/security_manager: remove unused functions
63c9862 Add local_auth_info_changed -> root_client_token_owner
76e924f Fix index in the loop of uw_crypto_utils_equal_
0de42f5 Add test to verify that token from different secret is not accepted
e08c7c6 Add macaroon auth token verification
cc77fad Add ClaimRootClientAuthToken and ConfirmRootClientAuthToken
29bc593 Use ClaimRootClientAuthToken instead of GetRootClientAuthToken
229113e Pass Config pointer into AuthManager
3eb52e7 Add the ability to query for particuar component and filter fields
0c190b3 Call ConfirmAuthToken when upload is done
a10ab1c Provide RootClientTokenOwner into ClaimRootClientAuthToken
ba01142 Remove unused kTokenDelimeter variable
Change-Id: I8bb0b536d6c28304255f27ed2738a57952ed0682
diff --git a/examples/daemon/examples.gyp b/examples/daemon/examples.gyp
index 2b497a5..d63c5e4 100644
--- a/examples/daemon/examples.gyp
+++ b/examples/daemon/examples.gyp
@@ -8,7 +8,8 @@
'light/daemon.gyp:weave_daemon_light',
'lock/daemon.gyp:weave_daemon_lock',
'ledflasher/daemon.gyp:weave_daemon_ledflasher',
- 'speaker/daemon.gyp:weave_daemon_speaker'
+ 'speaker/daemon.gyp:weave_daemon_speaker',
+ 'oven/daemon.gyp:weave_daemon_oven',
]
}
]
diff --git a/examples/daemon/oven/daemon.gyp b/examples/daemon/oven/daemon.gyp
new file mode 100644
index 0000000..44b4798
--- /dev/null
+++ b/examples/daemon/oven/daemon.gyp
@@ -0,0 +1,18 @@
+# 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.
+{
+ 'targets': [
+ {
+ 'target_name': 'weave_daemon_oven',
+ 'type': 'executable',
+ 'sources': [
+ 'oven.cc',
+ ],
+ 'dependencies': [
+ '<@(DEPTH)/libweave_standalone.gyp:libweave',
+ '<@(DEPTH)/examples/provider/provider.gyp:libweave_provider',
+ ]
+ }
+ ]
+}
diff --git a/examples/daemon/oven/oven.cc b/examples/daemon/oven/oven.cc
new file mode 100644
index 0000000..79fc925
--- /dev/null
+++ b/examples/daemon/oven/oven.cc
@@ -0,0 +1,263 @@
+// 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 "examples/daemon/common/daemon.h"
+
+#include <weave/device.h>
+#include <weave/provider/task_runner.h>
+
+#include <base/bind.h>
+#include <base/memory/weak_ptr.h>
+
+namespace {
+// Time for sensor temperature to match setting temperature
+const double kWarmUpTime = 60.0;
+// Oven max temp
+const double kMaxTemp = 300.0;
+// Oven min temp
+const double kMinTemp = 20.0;
+
+const char kTraits[] = R"({
+ "temperatureSetting": {
+ "commands": {
+ "setConfig": {
+ "minimalRole": "user",
+ "parameters": {
+ "units": {
+ "type": "string"
+ },
+ "tempSetting": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "state": {
+ "supportedUnits": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "celsius",
+ "fahrenheit",
+ "kelvin"
+ ]
+ }
+ },
+ "units": {
+ "type": "string"
+ },
+ "tempSetting": {
+ "type": "number"
+ },
+ "maxTempSetting": {
+ "type": "number"
+ },
+ "minTempSetting": {
+ "type": "number"
+ }
+ }
+ },
+ "temperatureSensor": {
+ "state": {
+ "supportedUnits": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "celsius",
+ "fahrenheit",
+ "kelvin"
+ ]
+ }
+ },
+ "units": {
+ "type": "string"
+ },
+ "value": {
+ "type": "number"
+ }
+ }
+ },
+ "brightness": {
+ "commands": {
+ "setConfig": {
+ "minimalRole": "user",
+ "parameters": {
+ "brightness": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 100
+ }
+ }
+ }
+ },
+ "state": {
+ "brightness": {
+ "type": "number"
+ }
+ }
+ }
+})";
+
+const char kComponent[] = "oven";
+} // anonymous namespace
+
+// OvenHandler is a virtual oven example
+// It implements the following commands from traits:
+// - temperatureSetting: sets the temperature for the oven
+// - brightness: sets the brightness of the oven light
+// It exposes the following states from traits:
+// - temperatureSetting: temperature setting for the oven
+// - temperatureSensor: current oven temperature
+// - brightness: current oven brightness
+class OvenHandler {
+ public:
+ OvenHandler(weave::provider::TaskRunner* task_runner)
+ : task_runner_{task_runner} {}
+
+ void Register(weave::Device* device) {
+ device_ = device;
+
+ device->AddTraitDefinitionsFromJson(kTraits);
+ CHECK(device->AddComponent(
+ kComponent, {"temperatureSetting", "temperatureSensor", "brightness"},
+ nullptr));
+
+ UpdateOvenState();
+
+ device->AddCommandHandler(kComponent, "temperatureSetting.setConfig",
+ base::Bind(&OvenHandler::OnSetTempCommand,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ device->AddCommandHandler(kComponent, "brightness.setConfig",
+ base::Bind(&OvenHandler::OnSetBrightnessCommand,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+private:
+ void OnSetTempCommand(const std::weak_ptr<weave::Command>& command) {
+ auto cmd = command.lock();
+ if (!cmd)
+ return;
+ LOG(INFO) << "received command: " << cmd->GetName();
+
+ const auto& params = cmd->GetParameters();
+ std::string units;
+ double temp;
+
+ if (params.GetString("units", &units) &&
+ params.GetDouble("tempSetting", &temp)) {
+ units_ = units;
+ target_temperature_ = temp;
+
+ UpdateOvenState();
+
+ cmd->Complete({}, nullptr);
+ LOG(INFO) << cmd->GetName() << " updated oven, matching temp";
+
+ if (target_temperature_ != current_temperature_ && !is_match_ticking_) {
+ double tickIncrement = ((target_temperature_ - current_temperature_) /
+ kWarmUpTime);
+ DoTick(tickIncrement);
+ }
+ return;
+ }
+
+ weave::ErrorPtr error;
+ weave::Error::AddTo(&error, FROM_HERE, "example", "invalid_parameter_value",
+ "Invalid parameters");
+ cmd->Abort(error.get(), nullptr);
+ }
+
+ void OnSetBrightnessCommand(const std::weak_ptr<weave::Command>& command) {
+ auto cmd = command.lock();
+ if (!cmd)
+ return;
+ LOG(INFO) << "received command: " << cmd->GetName();
+
+ const auto& params = cmd->GetParameters();
+
+ int brightness;
+ if (params.GetInteger("brightness", &brightness)) {
+ brightness_ = brightness;
+
+ UpdateOvenState();
+
+ cmd->Complete({}, nullptr);
+ return;
+ }
+
+ weave::ErrorPtr error;
+ weave::Error::AddTo(&error, FROM_HERE, "example", "invalid_parameter_value",
+ "Invalid parameters");
+ cmd->Abort(error.get(), nullptr);
+ }
+
+ void UpdateOvenState() {
+ base::DictionaryValue state;
+ base::ListValue supportedUnits;
+ supportedUnits.AppendStrings({"celsius"});
+
+ state.SetString("temperatureSensor.units", units_);
+ state.SetDouble("temperatureSensor.value", current_temperature_);
+ state.Set("temperatureSensor.supportedUnits", supportedUnits.DeepCopy());
+
+ state.SetString("temperatureSetting.units", units_);
+ state.SetDouble("temperatureSetting.tempSetting", target_temperature_);
+ state.Set("temperatureSetting.supportedUnits", supportedUnits.DeepCopy());
+ state.SetDouble("temperatureSetting.maxTempSetting", kMaxTemp);
+ state.SetDouble("temperatureSetting.minTempSetting", kMinTemp);
+
+ state.SetInteger("brightness.brightness", brightness_);
+
+ device_->SetStateProperties(kComponent, state, nullptr);
+ }
+
+ void DoTick(double tickIncrement) {
+ LOG(INFO) << "Oven matching temp tick";
+
+ if (std::fabs(target_temperature_ - current_temperature_) >=
+ tickIncrement) {
+ is_match_ticking_ = true;
+ current_temperature_ += tickIncrement;
+ UpdateOvenState();
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(&OvenHandler::DoTick,
+ weak_ptr_factory_.GetWeakPtr(), tickIncrement),
+ base::TimeDelta::FromSeconds(1));
+ return;
+ }
+
+ is_match_ticking_ = false;
+ current_temperature_ = target_temperature_;
+ UpdateOvenState();
+
+ LOG(INFO) << "Oven temp matched";
+ }
+
+ weave::Device* device_{nullptr};
+ weave::provider::TaskRunner* task_runner_{nullptr};
+
+ std::string units_ = "celsius";
+ double target_temperature_ = 0.0;
+ double current_temperature_ = 0.0;
+ int brightness_ = 0;
+ bool is_match_ticking_ = false;
+
+ base::WeakPtrFactory<OvenHandler> weak_ptr_factory_{this};
+};
+
+int main(int argc, char** argv) {
+ Daemon::Options opts;
+ if (!opts.Parse(argc, argv)) {
+ Daemon::Options::ShowUsage(argv[0]);
+ return 1;
+ }
+ Daemon daemon{opts};
+ OvenHandler handler{daemon.GetTaskRunner()};
+ handler.Register(daemon.GetDevice());
+ daemon.Run();
+ return 0;
+}
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc
index c63d2dd..c3cf445 100644
--- a/src/component_manager_unittest.cc
+++ b/src/component_manager_unittest.cc
@@ -1076,24 +1076,24 @@
manager_.AddStateChangedCallback(base::Bind([&count2]() { count2++; }));
EXPECT_EQ(1, count);
EXPECT_EQ(1, count2);
- EXPECT_EQ(0, manager_.GetLastStateChangeId());
+ EXPECT_EQ(0u, manager_.GetLastStateChangeId());
base::StringValue p1("foo");
ASSERT_TRUE(manager_.SetStateProperty("comp1", "trait1.prop1", p1, nullptr));
EXPECT_EQ(2, count);
EXPECT_EQ(2, count2);
- EXPECT_EQ(1, manager_.GetLastStateChangeId());
+ EXPECT_EQ(1u, manager_.GetLastStateChangeId());
ASSERT_TRUE(manager_.SetStateProperty("comp1", "trait1.prop2", p1, nullptr));
EXPECT_EQ(3, count);
EXPECT_EQ(3, count2);
- EXPECT_EQ(2, manager_.GetLastStateChangeId());
+ EXPECT_EQ(2u, manager_.GetLastStateChangeId());
// Fail - no component.
ASSERT_FALSE(manager_.SetStateProperty("comp2", "trait1.prop2", p1, nullptr));
EXPECT_EQ(3, count);
EXPECT_EQ(3, count2);
- EXPECT_EQ(2, manager_.GetLastStateChangeId());
+ EXPECT_EQ(2u, manager_.GetLastStateChangeId());
}
TEST_F(ComponentManagerTest, ComponentStateUpdates) {
diff --git a/src/config.cc b/src/config.cc
index f0ec963..18e50b5 100644
--- a/src/config.cc
+++ b/src/config.cc
@@ -42,7 +42,7 @@
const char kRobotAccount[] = "robot_account";
const char kLastConfiguredSsid[] = "last_configured_ssid";
const char kSecret[] = "secret";
-const char kLocalAuthInfoChanged[] = "local_auth_info_changed";
+const char kRootClientTokenOwner[] = "root_client_token_owner";
} // namespace config_keys
@@ -72,8 +72,18 @@
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} {
}
@@ -121,7 +131,7 @@
CHECK(settings_.robot_account.empty());
CHECK(settings_.last_configured_ssid.empty());
CHECK(settings_.secret.empty());
- CHECK(settings_.local_auth_info_changed);
+ CHECK(settings_.root_client_token_owner == RootClientTokenOwner::kNone);
change.LoadState();
}
@@ -214,8 +224,11 @@
if (dict->GetString(config_keys::kSecret, &tmp) && Base64Decode(tmp, &secret))
set_secret(secret);
- if (dict->GetBoolean(config_keys::kLocalAuthInfoChanged, &tmp_bool))
- set_local_auth_info_changed(tmp_bool);
+ 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() {
@@ -237,8 +250,8 @@
dict.SetString(config_keys::kLastConfiguredSsid,
settings_.last_configured_ssid);
dict.SetString(config_keys::kSecret, Base64Encode(settings_.secret));
- dict.SetBoolean(config_keys::kLocalAuthInfoChanged,
- settings_.local_auth_info_changed);
+ 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);
diff --git a/src/config.h b/src/config.h
index fc2568f..0c7ea5f 100644
--- a/src/config.h
+++ b/src/config.h
@@ -14,12 +14,18 @@
#include <weave/error.h>
#include <weave/provider/config_store.h>
-#include "src/privet/security_delegate.h"
+#include "src/privet/privet_types.h"
namespace weave {
class StorageInterface;
+enum class RootClientTokenOwner {
+ kNone,
+ kClient,
+ kCloud,
+};
+
// Handles reading buffet config and state files.
class Config final {
public:
@@ -28,7 +34,7 @@
std::string robot_account;
std::string last_configured_ssid;
std::vector<uint8_t> secret;
- bool local_auth_info_changed{true};
+ RootClientTokenOwner root_client_token_owner{RootClientTokenOwner::kNone};
};
using OnChangedCallback = base::Callback<void(const weave::Settings&)>;
@@ -92,8 +98,9 @@
void set_secret(const std::vector<uint8_t>& secret) {
settings_->secret = secret;
}
- void set_local_auth_info_changed(bool local_auth_info_changed) {
- settings_->local_auth_info_changed = local_auth_info_changed;
+ void set_root_client_token_owner(
+ RootClientTokenOwner root_client_token_owner) {
+ settings_->root_client_token_owner = root_client_token_owner;
}
void Commit();
diff --git a/src/config_unittest.cc b/src/config_unittest.cc
index 8c99131..1491d7b 100644
--- a/src/config_unittest.cc
+++ b/src/config_unittest.cc
@@ -77,7 +77,7 @@
EXPECT_EQ("", GetSettings().robot_account);
EXPECT_EQ("", GetSettings().last_configured_ssid);
EXPECT_EQ(std::vector<uint8_t>(), GetSettings().secret);
- EXPECT_TRUE(GetSettings().local_auth_info_changed);
+ EXPECT_EQ(RootClientTokenOwner::kNone, GetSettings().root_client_token_owner);
}
TEST_F(ConfigTest, LoadStateV0) {
@@ -117,7 +117,7 @@
"device_id": "state_device_id",
"last_configured_ssid": "state_last_configured_ssid",
"local_anonymous_access_role": "user",
- "local_auth_info_changed": false,
+ "root_client_token_owner": "client",
"local_discovery_enabled": false,
"local_pairing_enabled": false,
"location": "state_location",
@@ -161,7 +161,8 @@
EXPECT_EQ("state_robot_account", GetSettings().robot_account);
EXPECT_EQ("state_last_configured_ssid", GetSettings().last_configured_ssid);
EXPECT_EQ("c3RhdGVfc2VjcmV0", Base64Encode(GetSettings().secret));
- EXPECT_FALSE(GetSettings().local_auth_info_changed);
+ EXPECT_EQ(RootClientTokenOwner::kClient,
+ GetSettings().root_client_token_owner);
}
TEST_F(ConfigTest, Setters) {
@@ -231,8 +232,9 @@
change.set_secret(secret);
EXPECT_EQ(secret, GetSettings().secret);
- change.set_local_auth_info_changed(false);
- EXPECT_FALSE(GetSettings().local_auth_info_changed);
+ change.set_root_client_token_owner(RootClientTokenOwner::kCloud);
+ EXPECT_EQ(RootClientTokenOwner::kCloud,
+ GetSettings().root_client_token_owner);
EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
@@ -248,7 +250,7 @@
'device_id': 'set_device_id',
'last_configured_ssid': 'set_last_configured_ssid',
'local_anonymous_access_role': 'user',
- 'local_auth_info_changed': false,
+ 'root_client_token_owner': 'cloud',
'local_discovery_enabled': true,
'local_pairing_enabled': true,
'location': 'set_location',
diff --git a/src/device_manager.cc b/src/device_manager.cc
index cb575b8..88256da 100644
--- a/src/device_manager.cc
+++ b/src/device_manager.cc
@@ -33,15 +33,8 @@
config_->Load();
if (http_server) {
- auth_manager_.reset(
- new privet::AuthManager(config_->GetSettings().secret,
- http_server->GetHttpsCertificateFingerprint()));
-
- if (auth_manager_->GetSecret() != config_->GetSettings().secret) {
- // There is no Config::OnChangedCallback registered.
- Config::Transaction transaction(config_.get());
- transaction.set_secret(auth_manager_->GetSecret());
- }
+ auth_manager_.reset(new privet::AuthManager(
+ config_.get(), http_server->GetHttpsCertificateFingerprint()));
}
device_info_.reset(new DeviceRegistrationInfo(
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc
index a6d863c..f52c292 100644
--- a/src/device_registration_info.cc
+++ b/src/device_registration_info.cc
@@ -936,36 +936,34 @@
auth_info_update_inprogress_ = true;
std::string id = GetSettings().device_id;
- std::string token = Base64Encode(auth_manager_->GetRootDeviceToken());
+ std::vector<uint8_t> token =
+ auth_manager_->ClaimRootClientAuthToken(RootClientTokenOwner::kCloud);
+ std::string token_base64 = Base64Encode(token);
std::string fingerprint =
Base64Encode(auth_manager_->GetCertificateFingerprint());
std::unique_ptr<base::DictionaryValue> auth =
- BuildDeviceLocalAuth(id, token, fingerprint);
+ BuildDeviceLocalAuth(id, token_base64, fingerprint);
// TODO(vitalybuka): Remove args from URL when server is ready.
std::string url =
GetDeviceURL("upsertLocalAuthInfo", {{"localid", id},
- {"clienttoken", token},
+ {"clienttoken", token_base64},
{"certfingerprint", fingerprint}});
- DoCloudRequest(
- HttpClient::Method::kPost, url, auth.get(),
- base::Bind(&DeviceRegistrationInfo::OnSendAuthInfoDone, AsWeakPtr()));
+ DoCloudRequest(HttpClient::Method::kPost, url, auth.get(),
+ base::Bind(&DeviceRegistrationInfo::OnSendAuthInfoDone,
+ AsWeakPtr(), token));
}
void DeviceRegistrationInfo::OnSendAuthInfoDone(
+ const std::vector<uint8_t>& token,
const base::DictionaryValue& body,
ErrorPtr error) {
CHECK(auth_info_update_inprogress_);
auth_info_update_inprogress_ = false;
- if (!error) {
- // TODO(vitalybuka): Enable this when we start uploading real data.
- // Config::Transaction change{config_.get()};
- // change.set_local_auth_info_changed(false);
- // change.Commit();
+ if (!error && auth_manager_->ConfirmAuthToken(token))
return;
- }
task_runner_->PostDelayedTask(
FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendAuthInfo, AsWeakPtr()),
diff --git a/src/device_registration_info.h b/src/device_registration_info.h
index 04fb7b6..4a1c925 100644
--- a/src/device_registration_info.h
+++ b/src/device_registration_info.h
@@ -202,7 +202,9 @@
void OnUpdateDeviceResourceError(ErrorPtr error);
void SendAuthInfo();
- void OnSendAuthInfoDone(const base::DictionaryValue& body, ErrorPtr error);
+ void OnSendAuthInfoDone(const std::vector<uint8_t>& token,
+ const base::DictionaryValue& body,
+ ErrorPtr error);
// Callback from GetDeviceInfo() to retrieve the device resource timestamp
// and retry UpdateDeviceResource() call.
diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc
index cc519d2..66a9b1d 100644
--- a/src/device_registration_info_unittest.cc
+++ b/src/device_registration_info_unittest.cc
@@ -70,7 +70,6 @@
const char kAuthInfo[] = R"({
"certFingerprint":
"FQY6BEINDjw3FgsmYChRWgMzMhc4TC8uG0UUUFhdDz0=",
- "clientToken": "UBPkqttkiWt5VWgICLP0eHuCQgECRgMaVm0+gA==",
"localId": "f6885e46-b432-42d7-86a5-d759bfb61f62"
})";
@@ -269,7 +268,10 @@
.WillOnce(WithArgs<3, 4>(
Invoke([](const std::string& data,
const HttpClient::SendRequestCallback& callback) {
- EXPECT_JSON_EQ(test_data::kAuthInfo, *CreateDictionaryValue(data));
+ auto dict = CreateDictionaryValue(data);
+ EXPECT_TRUE(dict->HasKey("clientToken"));
+ dict->Remove("clientToken", nullptr);
+ EXPECT_JSON_EQ(test_data::kAuthInfo, *dict);
base::DictionaryValue json;
callback.Run(ReplyWithJson(200, json), nullptr);
})));
@@ -561,7 +563,10 @@
.WillOnce(WithArgs<3, 4>(
Invoke([](const std::string& data,
const HttpClient::SendRequestCallback& callback) {
- EXPECT_JSON_EQ(test_data::kAuthInfo, *CreateDictionaryValue(data));
+ auto dict = CreateDictionaryValue(data);
+ EXPECT_TRUE(dict->HasKey("clientToken"));
+ dict->Remove("clientToken", nullptr);
+ EXPECT_JSON_EQ(test_data::kAuthInfo, *dict);
base::DictionaryValue json;
callback.Run(ReplyWithJson(200, json), nullptr);
})));
diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc
index 49ef787..fa1d685 100644
--- a/src/privet/auth_manager.cc
+++ b/src/privet/auth_manager.cc
@@ -7,6 +7,7 @@
#include <base/rand_util.h>
#include <base/strings/string_number_conversions.h>
+#include "src/config.h"
#include "src/data_encoding.h"
#include "src/privet/openssl_utils.h"
#include "src/string_utils.h"
@@ -23,6 +24,7 @@
const char kTokenDelimeter[] = ":";
const size_t kCaveatBuffetSize = 32;
const size_t kMaxMacaroonSize = 1024;
+const size_t kMaxPendingClaims = 10;
// Returns "scope:id:time".
std::string CreateTokenData(const UserInfo& user_info, const base::Time& time) {
@@ -51,7 +53,8 @@
int64_t timestamp{0};
if (!base::StringToInt64(parts[2], ×tamp))
return kNone;
- *time = base::Time::FromTimeT(timestamp);
+ if (time)
+ *time = base::Time::FromTimeT(timestamp);
return UserInfo{static_cast<AuthScope>(scope), id};
}
@@ -71,18 +74,52 @@
DISALLOW_COPY_AND_ASSIGN(Caveat);
};
+std::vector<uint8_t> CreateSecret() {
+ std::vector<uint8_t> secret(kSha256OutputSize);
+ base::RandBytes(secret.data(), secret.size());
+ return secret;
+}
+
} // namespace
+AuthManager::AuthManager(Config* config,
+ const std::vector<uint8_t>& certificate_fingerprint)
+ : config_{config}, certificate_fingerprint_{certificate_fingerprint} {
+ if (config_) {
+ SetSecret(config_->GetSettings().secret,
+ config_->GetSettings().root_client_token_owner);
+ } else {
+ SetSecret({}, RootClientTokenOwner::kNone);
+ }
+}
+
AuthManager::AuthManager(const std::vector<uint8_t>& secret,
const std::vector<uint8_t>& certificate_fingerprint,
base::Clock* clock)
- : clock_{clock ? clock : &default_clock_},
- secret_{secret},
- certificate_fingerprint_{certificate_fingerprint} {
- if (secret_.size() != kSha256OutputSize) {
- secret_.resize(kSha256OutputSize);
- base::RandBytes(secret_.data(), secret_.size());
+ : AuthManager(nullptr, certificate_fingerprint) {
+ SetSecret(secret, RootClientTokenOwner::kNone);
+ if (clock)
+ clock_ = clock;
+}
+
+void AuthManager::SetSecret(const std::vector<uint8_t>& secret,
+ RootClientTokenOwner owner) {
+ secret_ = secret;
+
+ if (secret.size() != kSha256OutputSize) {
+ secret_ = CreateSecret();
+ owner = RootClientTokenOwner::kNone;
}
+
+ if (!config_ || (config_->GetSettings().secret == secret_ &&
+ config_->GetSettings().root_client_token_owner == owner)) {
+ return;
+ }
+
+ Config::Transaction change{config_};
+ change.set_secret(secret);
+ change.set_root_client_token_owner(owner);
+ change.Commit();
}
AuthManager::~AuthManager() {}
@@ -108,7 +145,34 @@
return SplitTokenData(std::string(data.begin(), data.end()), time);
}
-std::vector<uint8_t> AuthManager::GetRootDeviceToken() const {
+std::vector<uint8_t> AuthManager::ClaimRootClientAuthToken(
+ RootClientTokenOwner owner) {
+ pending_claims_.push_back(std::make_pair(
+ std::unique_ptr<AuthManager>{new AuthManager{nullptr, {}}}, owner));
+ if (pending_claims_.size() > kMaxPendingClaims)
+ pending_claims_.pop_front();
+ return pending_claims_.back().first->GetRootClientAuthToken();
+}
+
+bool AuthManager::ConfirmAuthToken(const std::vector<uint8_t>& token) {
+ // Cover case when caller sent confirm twice.
+ if (pending_claims_.empty() && IsValidAuthToken(token))
+ return true;
+
+ auto claim =
+ std::find_if(pending_claims_.begin(), pending_claims_.end(),
+ [&token](const decltype(pending_claims_)::value_type& auth) {
+ return auth.first->IsValidAuthToken(token);
+ });
+ if (claim == pending_claims_.end())
+ return false;
+
+ SetSecret(claim->first->GetSecret(), claim->second);
+ pending_claims_.clear();
+ return true;
+}
+
+std::vector<uint8_t> AuthManager::GetRootClientAuthToken() const {
Caveat scope{kUwMacaroonCaveatTypeScope, kUwMacaroonCaveatScopeTypeOwner};
Caveat issued{kUwMacaroonCaveatTypeIssued,
static_cast<uint32_t>(Now().ToTimeT())};
@@ -117,6 +181,7 @@
scope.GetCaveat(), issued.GetCaveat(),
};
+ CHECK_EQ(kSha256OutputSize, secret_.size());
UwMacaroon macaroon{};
CHECK(uw_macaroon_new_from_root_key_(
&macaroon, secret_.data(), secret_.size(), caveats, arraysize(caveats)));
@@ -132,5 +197,17 @@
return clock_->Now();
}
+bool AuthManager::IsValidAuthToken(const std::vector<uint8_t>& token) const {
+ std::vector<uint8_t> buffer(kMaxMacaroonSize);
+ UwMacaroon macaroon{};
+ if (!uw_macaroon_load_(token.data(), token.size(), buffer.data(),
+ buffer.size(), &macaroon)) {
+ return false;
+ }
+
+ CHECK_EQ(kSha256OutputSize, secret_.size());
+ return uw_macaroon_verify_(&macaroon, secret_.data(), secret_.size());
+}
+
} // namespace privet
} // namespace weave
diff --git a/src/privet/auth_manager.h b/src/privet/auth_manager.h
index b3a9599..62a1606 100644
--- a/src/privet/auth_manager.h
+++ b/src/privet/auth_manager.h
@@ -5,6 +5,7 @@
#ifndef LIBWEAVE_SRC_PRIVET_AUTH_MANAGER_H_
#define LIBWEAVE_SRC_PRIVET_AUTH_MANAGER_H_
+#include <deque>
#include <string>
#include <vector>
@@ -15,10 +16,18 @@
#include "src/privet/privet_types.h"
namespace weave {
+
+class Config;
+enum class RootClientTokenOwner;
+
namespace privet {
class AuthManager {
public:
+ AuthManager(Config* config,
+ const std::vector<uint8_t>& certificate_fingerprint);
+
+ // Constructor for tests.
AuthManager(const std::vector<uint8_t>& secret,
const std::vector<uint8_t>& certificate_fingerprint,
base::Clock* clock = nullptr);
@@ -32,17 +41,29 @@
const std::vector<uint8_t>& GetCertificateFingerprint() const {
return certificate_fingerprint_;
}
- std::vector<uint8_t> GetRootDeviceToken() const;
base::Time Now() const;
+ std::vector<uint8_t> ClaimRootClientAuthToken(RootClientTokenOwner owner);
+ bool ConfirmAuthToken(const std::vector<uint8_t>& token);
+
+ std::vector<uint8_t> GetRootClientAuthToken() const;
+ bool IsValidAuthToken(const std::vector<uint8_t>& token) const;
+
private:
+ void SetSecret(const std::vector<uint8_t>& secret,
+ RootClientTokenOwner owner);
+
+ Config* config_{nullptr};
base::DefaultClock default_clock_;
- base::Clock* clock_{nullptr};
+ base::Clock* clock_{&default_clock_};
std::vector<uint8_t> secret_;
std::vector<uint8_t> certificate_fingerprint_;
+ std::deque<std::pair<std::unique_ptr<AuthManager>, RootClientTokenOwner>>
+ pending_claims_;
+
DISALLOW_COPY_AND_ASSIGN(AuthManager);
};
diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc
index 4847e01..4dd753b 100644
--- a/src/privet/auth_manager_unittest.cc
+++ b/src/privet/auth_manager_unittest.cc
@@ -8,6 +8,7 @@
#include <gtest/gtest.h>
#include <weave/settings.h>
+#include "src/config.h"
#include "src/data_encoding.h"
#include "src/test/mock_clock.h"
@@ -108,6 +109,7 @@
auto token = auth.CreateAccessToken(UserInfo{AuthScope::kUser, 5});
base::Time time2;
+ EXPECT_EQ(AuthScope::kNone, auth_.ParseAccessToken(token, nullptr).scope());
EXPECT_EQ(AuthScope::kUser, auth.ParseAccessToken(token, &time2).scope());
EXPECT_EQ(5u, auth.ParseAccessToken(token, &time2).user_id());
// Token timestamp resolution is one second.
@@ -115,22 +117,62 @@
}
}
-TEST_F(AuthManagerTest, GetRootDeviceToken) {
+TEST_F(AuthManagerTest, GetRootClientAuthToken) {
EXPECT_EQ("UFTBUcgd9d0HnPRnLeroN2mCQgECRgMaVArkgA==",
- Base64Encode(auth_.GetRootDeviceToken()));
+ Base64Encode(auth_.GetRootClientAuthToken()));
}
-TEST_F(AuthManagerTest, GetRootDeviceTokenDifferentTime) {
+TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentTime) {
EXPECT_CALL(clock_, Now())
.WillRepeatedly(Return(clock_.Now() + base::TimeDelta::FromDays(15)));
EXPECT_EQ("UGKqwMYGQNOd8jeYFDOsM02CQgECRgMaVB6rAA==",
- Base64Encode(auth_.GetRootDeviceToken()));
+ Base64Encode(auth_.GetRootClientAuthToken()));
}
-TEST_F(AuthManagerTest, GetRootDeviceTokenDifferentSecret) {
+TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentSecret) {
AuthManager auth{kSecret2, {}, &clock_};
EXPECT_EQ("UK1ACOc3cWGjGBoTIX2bd3qCQgECRgMaVArkgA==",
- Base64Encode(auth.GetRootDeviceToken()));
+ Base64Encode(auth.GetRootClientAuthToken()));
+}
+
+TEST_F(AuthManagerTest, IsValidAuthToken) {
+ EXPECT_TRUE(auth_.IsValidAuthToken(auth_.GetRootClientAuthToken()));
+ // Multiple attempts with random secrets.
+ for (size_t i = 0; i < 1000; ++i) {
+ AuthManager auth{{}, {}, &clock_};
+
+ auto token = auth.GetRootClientAuthToken();
+ EXPECT_FALSE(auth_.IsValidAuthToken(token));
+ EXPECT_TRUE(auth.IsValidAuthToken(token));
+ }
+}
+
+TEST_F(AuthManagerTest, ClaimRootClientAuthToken) {
+ auto token = auth_.ClaimRootClientAuthToken(RootClientTokenOwner::kCloud);
+ EXPECT_FALSE(auth_.IsValidAuthToken(token));
+
+ EXPECT_TRUE(auth_.ConfirmAuthToken(token));
+ EXPECT_TRUE(auth_.IsValidAuthToken(token));
+}
+
+TEST_F(AuthManagerTest, ClaimRootClientAuthTokenDoubleConfirm) {
+ auto token = auth_.ClaimRootClientAuthToken(RootClientTokenOwner::kCloud);
+ EXPECT_TRUE(auth_.ConfirmAuthToken(token));
+ EXPECT_TRUE(auth_.ConfirmAuthToken(token));
+}
+
+TEST_F(AuthManagerTest, DoubleClaimRootClientAuthToken) {
+ auto token1 = auth_.ClaimRootClientAuthToken(RootClientTokenOwner::kCloud);
+ auto token2 = auth_.ClaimRootClientAuthToken(RootClientTokenOwner::kCloud);
+ EXPECT_TRUE(auth_.ConfirmAuthToken(token1));
+ EXPECT_FALSE(auth_.ConfirmAuthToken(token2));
+}
+
+TEST_F(AuthManagerTest, ClaimRootClientAuthTokenOverflow) {
+ auto token = auth_.ClaimRootClientAuthToken(RootClientTokenOwner::kCloud);
+ for (size_t i = 0; i < 100; ++i)
+ auth_.ClaimRootClientAuthToken(RootClientTokenOwner::kCloud);
+ EXPECT_FALSE(auth_.ConfirmAuthToken(token));
}
} // namespace privet
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc
index 170688b..3c3f0bb 100644
--- a/src/privet/cloud_delegate.cc
+++ b/src/privet/cloud_delegate.cc
@@ -28,7 +28,7 @@
const BackoffEntry::Policy register_backoff_policy = {0, 1000, 2.0, 0.2,
5000, -1, false};
-const int kMaxDeviceRegistrationTimeMinutes = 5;
+const int kMaxDeviceRegistrationRetries = 100; // ~ 8 minutes @5s retries.
CommandInstance* ReturnNotFound(const std::string& command_id,
ErrorPtr* error) {
@@ -113,22 +113,24 @@
bool Setup(const std::string& ticket_id,
const std::string& user,
ErrorPtr* error) override {
- if (setup_state_.IsStatusEqual(SetupState::kInProgress)) {
- Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kDeviceBusy,
- "Setup in progress");
- return false;
- }
VLOG(1) << "GCD Setup started. ticket_id: " << ticket_id
<< ", user:" << user;
+ // Set (or reset) the retry counter, since we are starting a new
+ // registration process.
+ registation_retry_count_ = kMaxDeviceRegistrationRetries;
+ ticket_id_ = ticket_id;
+ if (setup_state_.IsStatusEqual(SetupState::kInProgress)) {
+ // Another registration is in progress. In case it fails, we will use
+ // the new ticket ID when retrying the request.
+ return true;
+ }
setup_state_ = SetupState(SetupState::kInProgress);
setup_weak_factory_.InvalidateWeakPtrs();
backoff_entry_.Reset();
- base::Time deadline = base::Time::Now();
- deadline += base::TimeDelta::FromMinutes(kMaxDeviceRegistrationTimeMinutes);
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&CloudDelegateImpl::CallManagerRegisterDevice,
- setup_weak_factory_.GetWeakPtr(), ticket_id, deadline),
+ setup_weak_factory_.GetWeakPtr()),
{});
// Return true because we initiated setup.
return true;
@@ -152,6 +154,11 @@
return component_manager_->GetComponents();
}
+ const base::DictionaryValue* FindComponent(const std::string& path,
+ ErrorPtr* error) const override {
+ return component_manager_->FindComponent(path, error);
+ }
+
const base::DictionaryValue& GetTraits() const override {
return component_manager_->GetTraits();
}
@@ -259,10 +266,10 @@
setup_state_ = SetupState(SetupState::kSuccess);
}
- void CallManagerRegisterDevice(const std::string& ticket_id,
- const base::Time& deadline) {
+ void CallManagerRegisterDevice() {
ErrorPtr error;
- if (base::Time::Now() > deadline) {
+ CHECK_GE(registation_retry_count_, 0);
+ if (registation_retry_count_-- == 0) {
Error::AddTo(&error, FROM_HERE, errors::kDomain, errors::kInvalidState,
"Failed to register device");
setup_state_ = SetupState{std::move(error)};
@@ -270,21 +277,18 @@
}
device_->RegisterDevice(
- ticket_id,
- base::Bind(&CloudDelegateImpl::RegisterDeviceDone,
- setup_weak_factory_.GetWeakPtr(), ticket_id, deadline));
+ ticket_id_, base::Bind(&CloudDelegateImpl::RegisterDeviceDone,
+ setup_weak_factory_.GetWeakPtr()));
}
- void RegisterDeviceDone(const std::string& ticket_id,
- const base::Time& deadline,
- ErrorPtr error) {
+ void RegisterDeviceDone(ErrorPtr error) {
if (error) {
// Registration failed. Retry with backoff.
backoff_entry_.InformOfRequest(false);
return task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&CloudDelegateImpl::CallManagerRegisterDevice,
- setup_weak_factory_.GetWeakPtr(), ticket_id, deadline),
+ setup_weak_factory_.GetWeakPtr()),
backoff_entry_.GetTimeUntilRelease());
}
backoff_entry_.InformOfRequest(true);
@@ -335,6 +339,12 @@
// State of the current or last setup.
SetupState setup_state_{SetupState::kNone};
+ // Ticket ID for registering the device.
+ std::string ticket_id_;
+
+ // Number of remaining retries for device registration process.
+ int registation_retry_count_{0};
+
// Map of command IDs to user IDs.
std::map<std::string, uint64_t> command_owners_;
diff --git a/src/privet/cloud_delegate.h b/src/privet/cloud_delegate.h
index e80c39d..4aa9bcb 100644
--- a/src/privet/cloud_delegate.h
+++ b/src/privet/cloud_delegate.h
@@ -104,6 +104,11 @@
// Returns dictionary with component tree.
virtual const base::DictionaryValue& GetComponents() const = 0;
+ // Finds a component at the given path. Return nullptr in case of an error.
+ virtual const base::DictionaryValue* FindComponent(
+ const std::string& path,
+ ErrorPtr* error) const = 0;
+
// Returns dictionary with trait definitions.
virtual const base::DictionaryValue& GetTraits() const = 0;
diff --git a/src/privet/device_delegate.cc b/src/privet/device_delegate.cc
index 9a05483..5722357 100644
--- a/src/privet/device_delegate.cc
+++ b/src/privet/device_delegate.cc
@@ -30,10 +30,6 @@
std::pair<uint16_t, uint16_t> GetHttpsEnpoint() const override {
return std::make_pair(https_port_, https_port_);
}
- base::TimeDelta GetUptime() const override {
- return base::Time::Now() - start_time_;
- }
-
base::TimeDelta GetHttpRequestTimeout() const override {
return http_request_timeout_;
}
@@ -49,7 +45,6 @@
base::TimeDelta http_request_timeout_;
uint16_t http_port_{0};
uint16_t https_port_{0};
- base::Time start_time_{base::Time::Now()};
};
} // namespace
diff --git a/src/privet/device_delegate.h b/src/privet/device_delegate.h
index 1affa1b..9d9b186 100644
--- a/src/privet/device_delegate.h
+++ b/src/privet/device_delegate.h
@@ -35,9 +35,6 @@
// The same |GetHttpEnpoint| but for HTTPS.
virtual std::pair<uint16_t, uint16_t> GetHttpsEnpoint() const = 0;
- // Returns device uptime.
- virtual base::TimeDelta GetUptime() const = 0;
-
// Returns the max request timeout of http server. Returns TimeDelta::Max() if
// no timeout is set.
virtual base::TimeDelta GetHttpRequestTimeout() const = 0;
diff --git a/src/privet/mock_delegates.h b/src/privet/mock_delegates.h
index 5c003a7..6762481 100644
--- a/src/privet/mock_delegates.h
+++ b/src/privet/mock_delegates.h
@@ -44,7 +44,6 @@
public:
MOCK_CONST_METHOD0(GetHttpEnpoint, IntPair());
MOCK_CONST_METHOD0(GetHttpsEnpoint, IntPair());
- MOCK_CONST_METHOD0(GetUptime, base::TimeDelta());
MOCK_CONST_METHOD0(GetHttpRequestTimeout, base::TimeDelta());
MOCK_METHOD3(PostDelayedTask, void(const tracked_objects::Location&,
const base::Closure&,
@@ -55,8 +54,6 @@
.WillRepeatedly(Return(std::make_pair(0, 0)));
EXPECT_CALL(*this, GetHttpsEnpoint())
.WillRepeatedly(Return(std::make_pair(0, 0)));
- EXPECT_CALL(*this, GetUptime())
- .WillRepeatedly(Return(base::TimeDelta::FromHours(1)));
}
};
@@ -67,6 +64,8 @@
UserInfo(const std::string&, base::Time*));
MOCK_CONST_METHOD0(GetPairingTypes, std::set<PairingType>());
MOCK_CONST_METHOD0(GetCryptoTypes, std::set<CryptoType>());
+ MOCK_METHOD0(ClaimRootClientAuthToken, std::string());
+ MOCK_METHOD1(ConfirmAuthToken, bool(const std::string& token));
MOCK_CONST_METHOD1(IsValidPairingCode, bool(const std::string&));
MOCK_METHOD5(
StartPairing,
@@ -83,6 +82,9 @@
EXPECT_CALL(*this, CreateAccessToken(_))
.WillRepeatedly(Return("GuestAccessToken"));
+ EXPECT_CALL(*this, ClaimRootClientAuthToken())
+ .WillRepeatedly(Return("RootClientAuthToken"));
+
EXPECT_CALL(*this, ParseAccessToken(_, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(base::Time::Now()),
Return(UserInfo{AuthScope::kViewer, 1234567})));
@@ -155,6 +157,9 @@
MOCK_CONST_METHOD0(GetLegacyState, const base::DictionaryValue&());
MOCK_CONST_METHOD0(GetLegacyCommandDef, const base::DictionaryValue&());
MOCK_CONST_METHOD0(GetComponents, const base::DictionaryValue&());
+ MOCK_CONST_METHOD2(FindComponent,
+ const base::DictionaryValue*(const std::string& path,
+ ErrorPtr* error));
MOCK_CONST_METHOD0(GetTraits, const base::DictionaryValue&());
MOCK_METHOD3(AddCommand,
void(const base::DictionaryValue&,
@@ -191,6 +196,7 @@
GetLegacyCommandDef()).WillRepeatedly(ReturnRef(test_dict_));
EXPECT_CALL(*this, GetTraits()).WillRepeatedly(ReturnRef(test_dict_));
EXPECT_CALL(*this, GetComponents()).WillRepeatedly(ReturnRef(test_dict_));
+ EXPECT_CALL(*this, FindComponent(_, _)).Times(0);
}
ConnectionState connection_state_{ConnectionState::kOnline};
diff --git a/src/privet/privet_handler.cc b/src/privet/privet_handler.cc
index b71ea15..86a2a85 100644
--- a/src/privet/privet_handler.cc
+++ b/src/privet/privet_handler.cc
@@ -69,7 +69,7 @@
const char kInfoWifiSsidKey[] = "ssid";
const char kInfoWifiHostedSsidKey[] = "hostedSsid";
-const char kInfoUptimeKey[] = "uptime";
+const char kInfoTimeKey[] = "time";
const char kPairingKey[] = "pairing";
const char kPairingSessionIdKey[] = "sessionId";
@@ -106,6 +106,8 @@
const char kTraitsKey[] = "traits";
const char kComponentsKey[] = "components";
const char kCommandsIdKey[] = "id";
+const char kPathKey[] = "path";
+const char kFilterKey[] = "filter";
const char kStateFingerprintKey[] = "stateFingerprint";
const char kCommandsFingerprintKey[] = "commandsFingerprint";
@@ -319,6 +321,53 @@
return cloud.GetAnonymousMaxScope();
}
+// Forward-declaration.
+std::unique_ptr<base::DictionaryValue> CloneComponentTree(
+ const base::DictionaryValue& parent,
+ const std::set<std::string>& filter);
+
+// Clones a particular component JSON object in a manner similar to that of
+// DeepCopy(), except it includes only sub-objects specified in |filter| (if not
+// empty) and has special handling for "components" sub-dictionary.
+std::unique_ptr<base::DictionaryValue> CloneComponent(
+ const base::DictionaryValue& component,
+ const std::set<std::string>& filter) {
+ std::unique_ptr<base::DictionaryValue> clone{new base::DictionaryValue};
+ for (base::DictionaryValue::Iterator it(component); !it.IsAtEnd();
+ it.Advance()) {
+ if (filter.empty() || filter.find(it.key()) != filter.end()) {
+ if (it.key() == kComponentsKey) {
+ // Handle "components" separately as we need to recursively clone
+ // sub-components.
+ const base::DictionaryValue* sub_components = nullptr;
+ CHECK(it.value().GetAsDictionary(&sub_components));
+ clone->SetWithoutPathExpansion(
+ it.key(), CloneComponentTree(*sub_components, filter).release());
+ } else {
+ clone->SetWithoutPathExpansion(it.key(), it.value().DeepCopy());
+ }
+ }
+ }
+ return clone;
+}
+
+// Clones a dictionary containing a bunch of component JSON objects in a manner
+// similar to that of DeepCopy(). Calls CloneComponent() on each instance of
+// the component sub-object.
+std::unique_ptr<base::DictionaryValue> CloneComponentTree(
+ const base::DictionaryValue& parent,
+ const std::set<std::string>& filter) {
+ std::unique_ptr<base::DictionaryValue> clone{new base::DictionaryValue};
+ for (base::DictionaryValue::Iterator it(parent); !it.IsAtEnd();
+ it.Advance()) {
+ const base::DictionaryValue* component = nullptr;
+ CHECK(it.value().GetAsDictionary(&component));
+ clone->SetWithoutPathExpansion(
+ it.key(), CloneComponent(*component, filter).release());
+ }
+ return clone;
+}
+
} // namespace
std::vector<std::string> PrivetHandler::GetHttpPaths() const {
@@ -340,11 +389,14 @@
PrivetHandler::PrivetHandler(CloudDelegate* cloud,
DeviceDelegate* device,
SecurityDelegate* security,
- WifiDelegate* wifi)
- : cloud_(cloud), device_(device), security_(security), wifi_(wifi) {
+ WifiDelegate* wifi,
+ base::Clock* clock)
+ : cloud_(cloud), device_(device), security_(security), wifi_(wifi),
+ clock_(clock ? clock : &default_clock_) {
CHECK(cloud_);
CHECK(device_);
CHECK(security_);
+ CHECK(clock_);
cloud_observer_.Add(cloud_);
AddHandler("/privet/info", &PrivetHandler::HandleInfo, AuthScope::kNone);
@@ -466,7 +518,7 @@
time += base::TimeDelta::FromSeconds(kAccessTokenExpirationSeconds);
time +=
base::TimeDelta::FromSeconds(kAccessTokenExpirationThresholdSeconds);
- if (time < base::Time::Now()) {
+ if (time < clock_->Now()) {
Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
errors::kAuthorizationExpired, "Token expired: %s",
token.c_str());
@@ -543,7 +595,7 @@
output.Set(kGcdKey, CreateGcdSection(*cloud_).release());
- output.SetInteger(kInfoUptimeKey, device_->GetUptime().InSeconds());
+ output.SetDouble(kInfoTimeKey, clock_->Now().ToJsTime());
callback.Run(http::kOk, output);
}
@@ -682,6 +734,7 @@
output.SetString(kAuthTokenTypeKey, kAuthorizationHeaderPrefix);
output.SetInteger(kAuthExpiresInKey, kAccessTokenExpirationSeconds);
output.SetString(kAuthScopeKey, EnumToString(requested_auth_scope));
+
callback.Run(http::kOk, output);
}
@@ -802,8 +855,34 @@
void PrivetHandler::HandleComponents(const base::DictionaryValue& input,
const UserInfo& user_info,
const RequestCallback& callback) {
+ std::string path;
+ std::set<std::string> filter;
+ std::unique_ptr<base::DictionaryValue> components;
+
+ input.GetString(kPathKey, &path);
+ const base::ListValue* filter_items = nullptr;
+ if (input.GetList(kFilterKey, &filter_items)) {
+ for (const base::Value* value : *filter_items) {
+ std::string filter_item;
+ if (value->GetAsString(&filter_item))
+ filter.insert(filter_item);
+ }
+ }
+ const base::DictionaryValue* component = nullptr;
+ if (!path.empty()) {
+ ErrorPtr error;
+ component = cloud_->FindComponent(path, &error);
+ if (!component)
+ return ReturnError(*error, callback);
+ components.reset(new base::DictionaryValue);
+ // Get the last element of the path and use it as a dictionary key here.
+ auto parts = Split(path, ".", true, false);
+ components->Set(parts.back(), CloneComponent(*component, filter).release());
+ } else {
+ components = CloneComponentTree(cloud_->GetComponents(), filter);
+ }
base::DictionaryValue output;
- output.Set(kComponentsKey, cloud_->GetComponents().DeepCopy());
+ output.Set(kComponentsKey, components.release());
output.SetString(kFingerprintKey, std::to_string(components_fingerprint_));
callback.Run(http::kOk, output);
diff --git a/src/privet/privet_handler.h b/src/privet/privet_handler.h
index fc024d1..5241233 100644
--- a/src/privet/privet_handler.h
+++ b/src/privet/privet_handler.h
@@ -12,6 +12,7 @@
#include <base/macros.h>
#include <base/memory/weak_ptr.h>
#include <base/scoped_observer.h>
+#include <base/time/default_clock.h>
#include "src/privet/cloud_delegate.h"
@@ -42,7 +43,8 @@
PrivetHandler(CloudDelegate* cloud,
DeviceDelegate* device,
SecurityDelegate* pairing,
- WifiDelegate* wifi);
+ WifiDelegate* wifi,
+ base::Clock* clock = nullptr);
~PrivetHandler() override;
void OnTraitDefsChanged() override;
@@ -134,6 +136,8 @@
DeviceDelegate* device_{nullptr};
SecurityDelegate* security_{nullptr};
WifiDelegate* wifi_{nullptr};
+ base::DefaultClock default_clock_;
+ base::Clock* clock_{nullptr};
struct HandlerParameters {
ApiHandler handler;
diff --git a/src/privet/privet_handler_unittest.cc b/src/privet/privet_handler_unittest.cc
index 09146e8..20856f7 100644
--- a/src/privet/privet_handler_unittest.cc
+++ b/src/privet/privet_handler_unittest.cc
@@ -19,6 +19,7 @@
#include "src/privet/constants.h"
#include "src/privet/mock_delegates.h"
+#include "src/test/mock_clock.h"
using testing::_;
using testing::DoAll;
@@ -92,8 +93,12 @@
protected:
void SetUp() override {
+ EXPECT_CALL(clock_, Now())
+ .WillRepeatedly(Return(base::Time::FromTimeT(1410000001)));
+
auth_header_ = "Privet anonymous";
- handler_.reset(new PrivetHandler(&cloud_, &device_, &security_, &wifi_));
+ handler_.reset(new PrivetHandler(&cloud_, &device_, &security_, &wifi_,
+ &clock_));
}
const base::DictionaryValue& HandleRequest(
@@ -124,7 +129,8 @@
int GetResponseCount() const { return response_count_; }
void SetNoWifiAndGcd() {
- handler_.reset(new PrivetHandler(&cloud_, &device_, &security_, nullptr));
+ handler_.reset(new PrivetHandler(&cloud_, &device_, &security_, nullptr,
+ &clock_));
EXPECT_CALL(cloud_, GetCloudId()).WillRepeatedly(Return(""));
EXPECT_CALL(cloud_, GetConnectionState())
.WillRepeatedly(ReturnRef(gcd_disabled_state_));
@@ -136,6 +142,7 @@
.WillRepeatedly(DoAll(Invoke(set_error), Return(false)));
}
+ test::MockClock clock_;
testing::StrictMock<MockCloudDelegate> cloud_;
testing::StrictMock<MockDeviceDelegate> device_;
testing::StrictMock<MockSecurityDelegate> security_;
@@ -240,7 +247,7 @@
'id': '',
'status': 'disabled'
},
- 'uptime': 3600
+ 'time': 1410000001000.0
})";
EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/info", "{}"));
}
@@ -301,7 +308,7 @@
'id': 'TestCloudId',
'status': 'online'
},
- 'uptime': 3600
+ 'time': 1410000001000.0
})";
EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/info", "{}"));
}
@@ -636,6 +643,145 @@
HandleRequest("/privet/v3/components", "{}"));
}
+TEST_F(PrivetHandlerSetupTest, ComponentsWithFiltersAndPaths) {
+ const char kComponents[] = R"({
+ "comp1": {
+ "traits": ["a", "b"],
+ "state": {
+ "a" : {
+ "prop": 1
+ }
+ },
+ "components": {
+ "comp2": {
+ "traits": ["c"],
+ "components": {
+ "comp4": {
+ "traits": ["d"]
+ }
+ }
+ },
+ "comp3": {
+ "traits": ["e"]
+ }
+ }
+ }
+ })";
+ base::DictionaryValue components;
+ LoadTestJson(kComponents, &components);
+ EXPECT_CALL(cloud_, FindComponent(_, _)).WillRepeatedly(Return(nullptr));
+ EXPECT_CALL(cloud_, GetComponents()).WillRepeatedly(ReturnRef(components));
+ const char kExpected1[] = R"({
+ "components": {
+ "comp1": {
+ "state": {
+ "a" : {
+ "prop": 1
+ }
+ }
+ }
+ },
+ "fingerprint": "1"
+ })";
+ EXPECT_JSON_EQ(kExpected1, HandleRequest("/privet/v3/components",
+ "{'filter':['state']}"));
+
+ const char kExpected2[] = R"({
+ "components": {
+ "comp1": {
+ "traits": ["a", "b"]
+ }
+ },
+ "fingerprint": "1"
+ })";
+ EXPECT_JSON_EQ(kExpected2, HandleRequest("/privet/v3/components",
+ "{'filter':['traits']}"));
+
+ const char kExpected3[] = R"({
+ "components": {
+ "comp1": {
+ "components": {
+ "comp2": {
+ "components": {
+ "comp4": {}
+ }
+ },
+ "comp3": {}
+ }
+ }
+ },
+ "fingerprint": "1"
+ })";
+ EXPECT_JSON_EQ(kExpected3, HandleRequest("/privet/v3/components",
+ "{'filter':['components']}"));
+
+ const char kExpected4[] = R"({
+ "components": {
+ "comp1": {
+ "traits": ["a", "b"],
+ "state": {
+ "a" : {
+ "prop": 1
+ }
+ },
+ "components": {
+ "comp2": {
+ "traits": ["c"],
+ "components": {
+ "comp4": {
+ "traits": ["d"]
+ }
+ }
+ },
+ "comp3": {
+ "traits": ["e"]
+ }
+ }
+ }
+ },
+ "fingerprint": "1"
+ })";
+ EXPECT_JSON_EQ(kExpected4,
+ HandleRequest("/privet/v3/components",
+ "{'filter':['traits', 'components', 'state']}"));
+
+ const base::DictionaryValue* comp2 = nullptr;
+ ASSERT_TRUE(components.GetDictionary("comp1.components.comp2", &comp2));
+ EXPECT_CALL(cloud_, FindComponent("comp1.comp2", _))
+ .WillOnce(Return(comp2));
+
+ const char kExpected5[] = R"({
+ "components": {
+ "comp2": {
+ "traits": ["c"],
+ "components": {
+ "comp4": {
+ "traits": ["d"]
+ }
+ }
+ }
+ },
+ "fingerprint": "1"
+ })";
+ EXPECT_JSON_EQ(kExpected5, HandleRequest(
+ "/privet/v3/components",
+ "{'path':'comp1.comp2', 'filter':['traits', 'components']}"));
+
+ auto error_handler = [](ErrorPtr* error) -> const base::DictionaryValue* {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, "componentNotFound", "");
+ return nullptr;
+ };
+ EXPECT_CALL(cloud_, FindComponent("comp7", _))
+ .WillOnce(WithArgs<1>(Invoke(error_handler)));
+
+ EXPECT_PRED2(
+ IsEqualError,
+ CodeWithReason(500, "componentNotFound"),
+ HandleRequest(
+ "/privet/v3/components",
+ "{'path':'comp7', 'filter':['traits', 'components']}"));
+}
+
TEST_F(PrivetHandlerSetupTest, CommandsExecute) {
const char kInput[] = "{'name': 'test'}";
base::DictionaryValue command;
diff --git a/src/privet/security_delegate.h b/src/privet/security_delegate.h
index 051bf20..3446c48 100644
--- a/src/privet/security_delegate.h
+++ b/src/privet/security_delegate.h
@@ -34,6 +34,13 @@
// Returns list of crypto methods supported by devices.
virtual std::set<CryptoType> GetCryptoTypes() const = 0;
+ // Returns Root Client Authorization Token.
+ virtual std::string ClaimRootClientAuthToken() = 0;
+
+ // Confirms pending pending token claim or checks that token is valid for the
+ // active secret.
+ virtual bool ConfirmAuthToken(const std::string& token) = 0;
+
// Returns true if |auth_code| provided by client is valid. Client should
// obtain |auth_code| during pairing process.
virtual bool IsValidPairingCode(const std::string& auth_code) const = 0;
diff --git a/src/privet/security_manager.cc b/src/privet/security_manager.cc
index 00550d1..5fa41be 100644
--- a/src/privet/security_manager.cc
+++ b/src/privet/security_manager.cc
@@ -18,6 +18,7 @@
#include <base/time/time.h>
#include <weave/provider/task_runner.h>
+#include "src/config.h"
#include "src/data_encoding.h"
#include "src/privet/auth_manager.h"
#include "src/privet/constants.h"
@@ -30,43 +31,11 @@
namespace {
-const char kTokenDelimeter[] = ":";
const int kSessionExpirationTimeMinutes = 5;
const int kPairingExpirationTimeMinutes = 5;
const int kMaxAllowedPairingAttemts = 3;
const int kPairingBlockingTimeMinutes = 1;
-// Returns "scope:id:time".
-std::string CreateTokenData(const UserInfo& user_info, const base::Time& time) {
- return base::IntToString(static_cast<int>(user_info.scope())) +
- kTokenDelimeter + base::Uint64ToString(user_info.user_id()) +
- kTokenDelimeter + base::Int64ToString(time.ToTimeT());
-}
-
-// Splits string of "scope:id:time" format.
-UserInfo SplitTokenData(const std::string& token, base::Time* time) {
- const UserInfo kNone;
- auto parts = Split(token, kTokenDelimeter, false, false);
- if (parts.size() != 3)
- return kNone;
- int scope = 0;
- if (!base::StringToInt(parts[0], &scope) ||
- scope < static_cast<int>(AuthScope::kNone) ||
- scope > static_cast<int>(AuthScope::kOwner)) {
- return kNone;
- }
-
- uint64_t id{0};
- if (!base::StringToUint64(parts[1], &id))
- return kNone;
-
- int64_t timestamp{0};
- if (!base::StringToInt64(parts[2], ×tamp))
- return kNone;
- *time = base::Time::FromTimeT(timestamp);
- return UserInfo{static_cast<AuthScope>(scope), id};
-}
-
class Spakep224Exchanger : public SecurityManager::KeyExchanger {
public:
explicit Spakep224Exchanger(const std::string& password)
@@ -166,6 +135,18 @@
return result;
}
+std::string SecurityManager::ClaimRootClientAuthToken() {
+ return Base64Encode(
+ auth_manager_->ClaimRootClientAuthToken(RootClientTokenOwner::kClient));
+}
+
+bool SecurityManager::ConfirmAuthToken(const std::string& token) {
+ std::vector<uint8_t> token_decoded;
+ if (!Base64Decode(token, &token_decoded))
+ return false;
+ return auth_manager_->ConfirmAuthToken(token_decoded);
+}
+
bool SecurityManager::IsValidPairingCode(const std::string& auth_code) const {
if (is_security_disabled_)
return true;
diff --git a/src/privet/security_manager.h b/src/privet/security_manager.h
index 7a3c56a..5d9b75a 100644
--- a/src/privet/security_manager.h
+++ b/src/privet/security_manager.h
@@ -65,6 +65,8 @@
base::Time* time) const override;
std::set<PairingType> GetPairingTypes() const override;
std::set<CryptoType> GetCryptoTypes() const override;
+ std::string ClaimRootClientAuthToken() override;
+ bool ConfirmAuthToken(const std::string& token) override;
bool IsValidPairingCode(const std::string& auth_code) const override;
bool StartPairing(PairingType mode,
diff --git a/src/privet/security_manager_unittest.cc b/src/privet/security_manager_unittest.cc
index d9f5c56..7c26026 100644
--- a/src/privet/security_manager_unittest.cc
+++ b/src/privet/security_manager_unittest.cc
@@ -104,11 +104,11 @@
test::MockClock clock_;
AuthManager auth_manager_{
{},
- {{
+ {
59, 47, 77, 247, 129, 187, 188, 158, 172, 105, 246, 93, 102, 83, 8,
138, 176, 141, 37, 63, 223, 40, 153, 121, 134, 23, 120, 106, 24, 205,
7, 135,
- }},
+ },
&clock_};
SecurityManager security_{&auth_manager_,
{PairingType::kEmbeddedCode},
diff --git a/third_party/libuweave/src/crypto_utils.c b/third_party/libuweave/src/crypto_utils.c
index 75bd2e5..76b8068 100644
--- a/third_party/libuweave/src/crypto_utils.c
+++ b/third_party/libuweave/src/crypto_utils.c
@@ -16,7 +16,7 @@
uint8_t diff = 0;
for (size_t i = 0; i < len; i++) {
- diff |= arr1[0] ^ arr2[0];
+ diff |= arr1[i] ^ arr2[i];
}
return 0 == diff;