Merge remote-tracking branch 'weave/master' into 'weave/aosp-master'
483d597 Add 'sessionId' into privet/info
a0a8134 Changed AuthManager::CreateAccessToken and AuthManager::ParseAccessToken
f6a0fd9 examples/ledflasher: fix component
29bd070 Add AuthType enum with string mapping
fd2ef68 Move most of auth logic into SecurityDelegate::CreateAccessToken
4957afb Add privet API tests for local auth
3020c49 Remove unused SecurityDelegate::IsValidPairingCode
0bc02ed Use different secret for auth and access tokens
66f46b8 Change user_id into string
ee7322f Get list of auth modes from AuthManger
66a01e0 Add AuthManager::CreateAccessTokenFromAuth
3d6b552 Hide Config::Load method
20896ab Replace several members of SecurityManager with pointers to Settings
7a25a3d Extracted function to create token from secret and caveats
131b889 Extract macaroon reading and verifying into separate functions
e0df73a Switch to macaroon library to generate and parse access tokens
ce850b5 Add the ability to remove a component from component tree
Change-Id: Id1a2ac3e0852660009e6bc0773db891e335a117c
diff --git a/examples/daemon/ledflasher/ledflasher.cc b/examples/daemon/ledflasher/ledflasher.cc
index d26728f..2e38d95 100644
--- a/examples/daemon/ledflasher/ledflasher.cc
+++ b/examples/daemon/ledflasher/ledflasher.cc
@@ -49,7 +49,7 @@
}
})";
-const char kComponent[] = "lock";
+const char kComponent[] = "ledflasher";
} // namespace
diff --git a/include/weave/device.h b/include/weave/device.h
index 069e688..05e3b78 100644
--- a/include/weave/device.h
+++ b/include/weave/device.h
@@ -62,6 +62,10 @@
const std::vector<std::string>& traits,
ErrorPtr* error) = 0;
+ // Removes an existing component instance from device.
+ virtual bool RemoveComponent(const std::string& name,
+ ErrorPtr* error) = 0;
+
// Sets callback which is called when new components are added.
virtual void AddComponentTreeChangedCallback(
const base::Closure& callback) = 0;
diff --git a/include/weave/provider/test/mock_config_store.h b/include/weave/provider/test/mock_config_store.h
index 01e1ef9..3873251 100644
--- a/include/weave/provider/test/mock_config_store.h
+++ b/include/weave/provider/test/mock_config_store.h
@@ -35,7 +35,10 @@
return true;
}));
EXPECT_CALL(*this, LoadSettings())
- .WillRepeatedly(Return(R"({"device_id": "TEST_DEVICE_ID"})"));
+ .WillRepeatedly(Return(R"({
+ "version": 1,
+ "device_id": "TEST_DEVICE_ID"
+ })"));
EXPECT_CALL(*this, SaveSettings(_)).WillRepeatedly(Return());
}
MOCK_METHOD1(LoadDefaults, bool(Settings*));
diff --git a/include/weave/test/mock_device.h b/include/weave/test/mock_device.h
index ddd3f59..2f380e0 100644
--- a/include/weave/test/mock_device.h
+++ b/include/weave/test/mock_device.h
@@ -30,6 +30,7 @@
bool(const std::string& name,
const std::vector<std::string>& traits,
ErrorPtr* error));
+ MOCK_METHOD2(RemoveComponent, bool(const std::string& name, ErrorPtr* error));
MOCK_METHOD1(AddComponentTreeChangedCallback,
void(const base::Closure& callback));
MOCK_CONST_METHOD0(GetComponents, const base::DictionaryValue&());
diff --git a/src/base_api_handler_unittest.cc b/src/base_api_handler_unittest.cc
index 6f8460b..8b0f0b2 100644
--- a/src/base_api_handler_unittest.cc
+++ b/src/base_api_handler_unittest.cc
@@ -54,7 +54,6 @@
.WillRepeatedly(
Invoke(&component_manager_, &ComponentManager::AddCommandHandler));
- config_.Load();
dev_reg_.reset(new DeviceRegistrationInfo(&config_, &component_manager_,
nullptr, &http_client_, nullptr,
nullptr));
diff --git a/src/component_manager.h b/src/component_manager.h
index cf16720..832b274 100644
--- a/src/component_manager.h
+++ b/src/component_manager.h
@@ -85,6 +85,22 @@
const std::vector<std::string>& traits,
ErrorPtr* error) = 0;
+ // Removes an existing component instance from device.
+ // |path| is a path to the parent component (or empty string if a root-level
+ // component is being removed).
+ // |name| is a name of the component to be removed.
+ virtual bool RemoveComponent(const std::string& path,
+ const std::string& name,
+ ErrorPtr* error) = 0;
+
+ // Removes an element from component array.
+ // |path| is a path to the parent component.
+ // |index| is a zero-based element index in the component array.
+ virtual bool RemoveComponentArrayItem(const std::string& path,
+ const std::string& name,
+ size_t index,
+ ErrorPtr* error) = 0;
+
// Sets callback which is called when new components are added.
virtual void AddComponentTreeChangedCallback(
const base::Closure& callback) = 0;
diff --git a/src/component_manager_impl.cc b/src/component_manager_impl.cc
index b1f3289..3f6ebf9 100644
--- a/src/component_manager_impl.cc
+++ b/src/component_manager_impl.cc
@@ -99,6 +99,63 @@
return true;
}
+bool ComponentManagerImpl::RemoveComponent(const std::string& path,
+ const std::string& name,
+ ErrorPtr* error) {
+ base::DictionaryValue* root = &components_;
+ if (!path.empty()) {
+ root = FindComponentGraftNode(path, error);
+ if (!root)
+ return false;
+ }
+
+ if (!root->RemoveWithoutPathExpansion(name, nullptr)) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidState,
+ "Component '%s' does not exist at path '%s'",
+ name.c_str(), path.c_str());
+ return false;
+ }
+
+ for (const auto& cb : on_componet_tree_changed_)
+ cb.Run();
+ return true;
+}
+
+bool ComponentManagerImpl::RemoveComponentArrayItem(const std::string& path,
+ const std::string& name,
+ size_t index,
+ ErrorPtr* error) {
+ base::DictionaryValue* root = &components_;
+ if (!path.empty()) {
+ root = FindComponentGraftNode(path, error);
+ if (!root)
+ return false;
+ }
+
+ base::ListValue* array_value = nullptr;
+ if (!root->GetListWithoutPathExpansion(name, &array_value)) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidState,
+ "There is no component array named '%s' at path '%s'",
+ name.c_str(), path.c_str());
+ return false;
+ }
+
+ if (!array_value->Remove(index, nullptr)) {
+ Error::AddToPrintf(
+ error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidState,
+ "Component array '%s' at path '%s' does not have an element %zu",
+ name.c_str(), path.c_str(), index);
+ return false;
+ }
+
+ for (const auto& cb : on_componet_tree_changed_)
+ cb.Run();
+ return true;
+}
+
void ComponentManagerImpl::AddComponentTreeChangedCallback(
const base::Closure& callback) {
on_componet_tree_changed_.push_back(callback);
diff --git a/src/component_manager_impl.h b/src/component_manager_impl.h
index 97d302d..8c4ad16 100644
--- a/src/component_manager_impl.h
+++ b/src/component_manager_impl.h
@@ -47,6 +47,23 @@
const std::vector<std::string>& traits,
ErrorPtr* error) override;
+ // Removes an existing component instance from device.
+ // |path| is a path to the parent component (or empty string if a root-level
+ // component is being removed).
+ // |name| is a name of the component to be removed.
+ bool RemoveComponent(const std::string& path,
+ const std::string& name,
+ ErrorPtr* error) override;
+
+ // Removes an element from component array.
+ // |path| is a path to the parent component.
+ // |name| is an array root element inside the child components.
+ // |index| is a zero-based element index in the component array.
+ bool RemoveComponentArrayItem(const std::string& path,
+ const std::string& name,
+ size_t index,
+ ErrorPtr* error) override;
+
// Sets callback which is called when new components are added.
void AddComponentTreeChangedCallback(const base::Closure& callback) override;
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc
index 6b660b1..519b80a 100644
--- a/src/component_manager_unittest.cc
+++ b/src/component_manager_unittest.cc
@@ -519,6 +519,48 @@
EXPECT_JSON_EQ(kExpected, manager_.GetComponents());
}
+TEST_F(ComponentManagerTest, RemoveComponent) {
+ CreateTestComponentTree(&manager_);
+ EXPECT_TRUE(manager_.RemoveComponent("comp1.comp2[1].comp3", "comp4",
+ nullptr));
+ const char kExpected1[] = R"({
+ "comp1": {
+ "traits": [ "t1" ],
+ "components": {
+ "comp2": [
+ {
+ "traits": [ "t2" ]
+ },
+ {
+ "traits": [ "t3" ],
+ "components": {
+ "comp3": {
+ "traits": [ "t4" ],
+ "components": {}
+ }
+ }
+ }
+ ]
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(kExpected1, manager_.GetComponents());
+ EXPECT_TRUE(manager_.RemoveComponentArrayItem("comp1", "comp2", 1, nullptr));
+ const char kExpected2[] = R"({
+ "comp1": {
+ "traits": [ "t1" ],
+ "components": {
+ "comp2": [
+ {
+ "traits": [ "t2" ]
+ }
+ ]
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(kExpected2, manager_.GetComponents());
+}
+
TEST_F(ComponentManagerTest, AddComponentExist) {
EXPECT_TRUE(manager_.AddComponent("", "comp1", {}, nullptr));
EXPECT_FALSE(manager_.AddComponent("", "comp1", {}, nullptr));
@@ -548,6 +590,10 @@
EXPECT_EQ(5, count);
EXPECT_TRUE(manager_.AddComponentArrayItem("comp1", "comp3", {}, nullptr));
EXPECT_EQ(6, count);
+ EXPECT_TRUE(manager_.RemoveComponentArrayItem("comp1", "comp3", 1, nullptr));
+ EXPECT_EQ(7, count);
+ EXPECT_TRUE(manager_.RemoveComponent("", "comp1", nullptr));
+ EXPECT_EQ(8, count);
// Make sure both callbacks were called the same number of times.
EXPECT_EQ(count2, count);
}
diff --git a/src/config.cc b/src/config.cc
index 442a87a..37e907c 100644
--- a/src/config.cc
+++ b/src/config.cc
@@ -85,7 +85,9 @@
: EnumToStringMap(kRootClientTokenOwnerMap) {}
Config::Config(provider::ConfigStore* config_store)
- : settings_{CreateDefaultSettings()}, config_store_{config_store} {}
+ : settings_{CreateDefaultSettings()}, config_store_{config_store} {
+ Load();
+}
void Config::AddOnChangedCallback(const OnChangedCallback& callback) {
on_changed_.push_back(callback);
diff --git a/src/config.h b/src/config.h
index f5a8a77..6dc0a07 100644
--- a/src/config.h
+++ b/src/config.h
@@ -46,8 +46,6 @@
void AddOnChangedCallback(const OnChangedCallback& callback);
const Config::Settings& GetSettings() const;
- void Load();
-
// Allows editing of config. Makes sure that callbacks were called and changes
// were saved.
// User can commit changes by calling Commit method or by destroying the
@@ -120,6 +118,7 @@
};
private:
+ void Load();
void Save();
Settings settings_;
diff --git a/src/config_unittest.cc b/src/config_unittest.cc
index 1491d7b..0367516 100644
--- a/src/config_unittest.cc
+++ b/src/config_unittest.cc
@@ -25,12 +25,16 @@
void SetUp() override {
EXPECT_CALL(*this, OnConfigChanged(_))
.Times(1); // Called from AddOnChangedCallback
- config_.AddOnChangedCallback(
- base::Bind(&ConfigTest::OnConfigChanged, base::Unretained(this)));
- default_.Load();
+ Reload();
}
- const Config::Settings& GetSettings() const { return config_.GetSettings(); }
+ void Reload() {
+ config_.reset(new Config{&config_store_});
+ config_->AddOnChangedCallback(
+ base::Bind(&ConfigTest::OnConfigChanged, base::Unretained(this)));
+ }
+
+ const Config::Settings& GetSettings() const { return config_->GetSettings(); }
const Config::Settings& GetDefaultSettings() const {
return default_.GetSettings();
@@ -39,7 +43,7 @@
MOCK_METHOD1(OnConfigChanged, void(const Settings&));
provider::test::MockConfigStore config_store_;
- Config config_{&config_store_};
+ std::unique_ptr<Config> config_{new Config{nullptr}};
Config default_{&config_store_};
};
@@ -50,6 +54,7 @@
}
TEST_F(ConfigTest, Defaults) {
+ config_.reset(new Config{nullptr});
EXPECT_EQ("", GetSettings().client_id);
EXPECT_EQ("", GetSettings().client_secret);
EXPECT_EQ("", GetSettings().api_key);
@@ -87,7 +92,7 @@
})"));
EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
- config_.Load();
+ Reload();
EXPECT_EQ("state_device_id", GetSettings().cloud_id);
EXPECT_FALSE(GetSettings().device_id.empty());
@@ -100,7 +105,7 @@
})"));
EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
- config_.Load();
+ Reload();
EXPECT_EQ("state_cloud_id", GetSettings().cloud_id);
EXPECT_EQ("state_device_id", GetSettings().device_id);
@@ -131,7 +136,7 @@
EXPECT_CALL(config_store_, LoadSettings()).WillOnce(Return(state));
EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
- config_.Load();
+ Reload();
EXPECT_EQ("state_client_id", GetSettings().client_id);
EXPECT_EQ("state_client_secret", GetSettings().client_secret);
@@ -166,7 +171,7 @@
}
TEST_F(ConfigTest, Setters) {
- Config::Transaction change{&config_};
+ Config::Transaction change{config_.get()};
change.set_client_id("set_client_id");
EXPECT_EQ("set_client_id", GetSettings().client_id);
diff --git a/src/device_manager.cc b/src/device_manager.cc
index ab2e225..1158df7 100644
--- a/src/device_manager.cc
+++ b/src/device_manager.cc
@@ -30,8 +30,6 @@
provider::Bluetooth* bluetooth)
: config_{new Config{config_store}},
component_manager_{new ComponentManagerImpl} {
- config_->Load();
-
if (http_server) {
auth_manager_.reset(new privet::AuthManager(
config_.get(), http_server->GetHttpsCertificateFingerprint()));
@@ -108,6 +106,10 @@
return component_manager_->AddComponent("", name, traits, error);
}
+bool DeviceManager::RemoveComponent(const std::string& name, ErrorPtr* error) {
+ return component_manager_->RemoveComponent("", name, error);
+}
+
void DeviceManager::AddComponentTreeChangedCallback(
const base::Closure& callback) {
component_manager_->AddComponentTreeChangedCallback(callback);
diff --git a/src/device_manager.h b/src/device_manager.h
index d21f398..d40ba8e 100644
--- a/src/device_manager.h
+++ b/src/device_manager.h
@@ -43,6 +43,7 @@
bool AddComponent(const std::string& name,
const std::vector<std::string>& traits,
ErrorPtr* error) override;
+ bool RemoveComponent(const std::string& name, ErrorPtr* error) override;
void AddComponentTreeChangedCallback(const base::Closure& callback) override;
const base::DictionaryValue& GetComponents() const override;
bool SetStatePropertiesFromJson(const std::string& component,
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc
index 445c138..a91ae6f 100644
--- a/src/device_registration_info.cc
+++ b/src/device_registration_info.cc
@@ -1321,7 +1321,7 @@
LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials";
if (auth_manager_)
- auth_manager_->SetSecret({}, RootClientTokenOwner::kNone);
+ auth_manager_->SetAuthSecret({}, RootClientTokenOwner::kNone);
Config::Transaction change{config_};
// Keep cloud_id to switch to detect kInvalidCredentials after restart.
diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc
index 808f545..ea81057 100644
--- a/src/device_registration_info_unittest.cc
+++ b/src/device_registration_info_unittest.cc
@@ -126,10 +126,6 @@
void SetUp() override {
EXPECT_CALL(clock_, Now())
.WillRepeatedly(Return(base::Time::FromTimeT(1450000000)));
- dev_reg_.reset(new DeviceRegistrationInfo{&config_, &component_manager_,
- &task_runner_, &http_client_,
- nullptr, &auth_});
-
ReloadDefaults();
}
@@ -150,7 +146,10 @@
settings->service_url = test_data::kServiceURL;
return true;
}));
- config_.Load();
+ config_.reset(new Config{&config_store_});
+ dev_reg_.reset(new DeviceRegistrationInfo{
+ config_.get(), &component_manager_, &task_runner_, &http_client_,
+ nullptr, &auth_});
dev_reg_->Start();
}
@@ -196,13 +195,14 @@
provider::test::MockConfigStore config_store_;
StrictMock<MockHttpClient> http_client_;
base::DictionaryValue data_;
- Config config_{&config_store_};
+ std::unique_ptr<Config> config_;
test::MockClock clock_;
privet::AuthManager auth_{
{68, 52, 36, 95, 74, 89, 25, 2, 31, 5, 65, 87, 64, 32, 17, 26, 8, 73, 57,
16, 33, 82, 71, 10, 72, 62, 45, 1, 77, 97, 70, 24},
{21, 6, 58, 4, 66, 13, 14, 60, 55, 22, 11, 38, 96, 40, 81, 90, 3, 51, 50,
23, 56, 76, 47, 46, 27, 69, 20, 80, 88, 93, 15, 61},
+ {},
&clock_};
std::unique_ptr<DeviceRegistrationInfo> dev_reg_;
ComponentManagerImpl component_manager_;
diff --git a/src/mock_component_manager.h b/src/mock_component_manager.h
index 08c1e59..addd6f0 100644
--- a/src/mock_component_manager.h
+++ b/src/mock_component_manager.h
@@ -28,6 +28,15 @@
const std::string& name,
const std::vector<std::string>& traits,
ErrorPtr* error));
+ MOCK_METHOD3(RemoveComponent,
+ bool(const std::string& path,
+ const std::string& name,
+ ErrorPtr* error));
+ MOCK_METHOD4(RemoveComponentArrayItem,
+ bool(const std::string& path,
+ const std::string& name,
+ size_t index,
+ ErrorPtr* error));
MOCK_METHOD1(AddComponentTreeChangedCallback,
void(const base::Closure& callback));
MOCK_METHOD1(MockAddCommand, void(CommandInstance* command_instance));
diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc
index 62a640f..8373a7e 100644
--- a/src/privet/auth_manager.cc
+++ b/src/privet/auth_manager.cc
@@ -4,6 +4,9 @@
#include "src/privet/auth_manager.h"
+#include <algorithm>
+
+#include <base/guid.h>
#include <base/rand_util.h>
#include <base/strings/string_number_conversions.h>
@@ -22,59 +25,95 @@
namespace {
-const char kTokenDelimeter[] = ":";
-const size_t kCaveatBuffetSize = 32;
const size_t kMaxMacaroonSize = 1024;
const size_t kMaxPendingClaims = 10;
+const char kInvalidTokenError[] = "invalid_token";
-// 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;
- if (time)
- *time = base::Time::FromTimeT(timestamp);
- return UserInfo{static_cast<AuthScope>(scope), id};
+template <class T>
+void AppendToArray(T value, std::vector<uint8_t>* array) {
+ auto begin = reinterpret_cast<const uint8_t*>(&value);
+ array->insert(array->end(), begin, begin + sizeof(value));
}
class Caveat {
public:
- Caveat(UwMacaroonCaveatType type, uint32_t value) {
- CHECK(uw_macaroon_caveat_create_with_uint_(type, value, buffer,
- sizeof(buffer), &caveat));
+ // TODO(vitalybuka): Use _get_buffer_size_ when available.
+ Caveat(UwMacaroonCaveatType type, uint32_t value) : buffer(8) {
+ CHECK(uw_macaroon_caveat_create_with_uint_(type, value, buffer.data(),
+ buffer.size(), &caveat));
+ }
+
+ // TODO(vitalybuka): Use _get_buffer_size_ when available.
+ Caveat(UwMacaroonCaveatType type, const std::string& value)
+ : buffer(std::max<size_t>(value.size(), 32u) * 2) {
+ CHECK(uw_macaroon_caveat_create_with_str_(
+ type, reinterpret_cast<const uint8_t*>(value.data()), value.size(),
+ buffer.data(), buffer.size(), &caveat));
}
const UwMacaroonCaveat& GetCaveat() const { return caveat; }
private:
UwMacaroonCaveat caveat;
- uint8_t buffer[kCaveatBuffetSize];
+ std::vector<uint8_t> buffer;
DISALLOW_COPY_AND_ASSIGN(Caveat);
};
+bool CheckCaveatType(const UwMacaroonCaveat& caveat,
+ UwMacaroonCaveatType type,
+ ErrorPtr* error) {
+ UwMacaroonCaveatType caveat_type{};
+ if (!uw_macaroon_caveat_get_type_(&caveat, &caveat_type)) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+ "Unable to get type");
+ return false;
+ }
+
+ if (caveat_type != type) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+ "Unexpected caveat type");
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadCaveat(const UwMacaroonCaveat& caveat,
+ UwMacaroonCaveatType type,
+ uint32_t* value,
+ ErrorPtr* error) {
+ if (!CheckCaveatType(caveat, type, error))
+ return false;
+
+ if (!uw_macaroon_caveat_get_value_uint_(&caveat, value)) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+ "Unable to read caveat");
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadCaveat(const UwMacaroonCaveat& caveat,
+ UwMacaroonCaveatType type,
+ std::string* value,
+ ErrorPtr* error) {
+ if (!CheckCaveatType(caveat, type, error))
+ return false;
+
+ const uint8_t* start{nullptr};
+ size_t size{0};
+ if (!uw_macaroon_caveat_get_value_str_(&caveat, &start, &size)) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+ "Unable to read caveat");
+ return false;
+ }
+
+ value->assign(reinterpret_cast<const char*>(start), size);
+ return true;
+}
+
std::vector<uint8_t> CreateSecret() {
std::vector<uint8_t> secret(kSha256OutputSize);
base::RandBytes(secret.data(), secret.size());
@@ -85,38 +124,113 @@
return claimer > curret || claimer == RootClientTokenOwner::kCloud;
}
+std::vector<uint8_t> CreateMacaroonToken(
+ const std::vector<uint8_t>& secret,
+ const std::vector<UwMacaroonCaveat>& caveats) {
+ CHECK_EQ(kSha256OutputSize, secret.size());
+ UwMacaroon macaroon{};
+ CHECK(uw_macaroon_new_from_root_key_(&macaroon, secret.data(), secret.size(),
+ caveats.data(), caveats.size()));
+
+ std::vector<uint8_t> token(kMaxMacaroonSize);
+ size_t len = 0;
+ CHECK(uw_macaroon_dump_(&macaroon, token.data(), token.size(), &len));
+ token.resize(len);
+
+ return token;
+}
+
+bool LoadMacaroon(const std::vector<uint8_t>& token,
+ std::vector<uint8_t>* buffer,
+ UwMacaroon* macaroon,
+ ErrorPtr* error) {
+ buffer->resize(kMaxMacaroonSize);
+ if (!uw_macaroon_load_(token.data(), token.size(), buffer->data(),
+ buffer->size(), macaroon)) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+ "Invalid token format");
+ return false;
+ }
+ return true;
+}
+
+bool VerifyMacaroon(const std::vector<uint8_t>& secret,
+ const UwMacaroon& macaroon,
+ ErrorPtr* error) {
+ CHECK_EQ(kSha256OutputSize, secret.size());
+ if (!uw_macaroon_verify_(&macaroon, secret.data(), secret.size())) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, "invalid_signature",
+ "Invalid token signature");
+ return false;
+ }
+ return true;
+}
+
+UwMacaroonCaveatScopeType ToMacaroonScope(AuthScope scope) {
+ switch (scope) {
+ case AuthScope::kViewer:
+ return kUwMacaroonCaveatScopeTypeViewer;
+ case AuthScope::kUser:
+ return kUwMacaroonCaveatScopeTypeUser;
+ case AuthScope::kManager:
+ return kUwMacaroonCaveatScopeTypeManager;
+ case AuthScope::kOwner:
+ return kUwMacaroonCaveatScopeTypeOwner;
+ default:
+ NOTREACHED() << EnumToString(scope);
+ }
+ return kUwMacaroonCaveatScopeTypeViewer;
+}
+
+AuthScope FromMacaroonScope(uint32_t scope) {
+ if (scope <= kUwMacaroonCaveatScopeTypeOwner)
+ return AuthScope::kOwner;
+ if (scope <= kUwMacaroonCaveatScopeTypeManager)
+ return AuthScope::kManager;
+ if (scope <= kUwMacaroonCaveatScopeTypeUser)
+ return AuthScope::kUser;
+ if (scope <= kUwMacaroonCaveatScopeTypeViewer)
+ return AuthScope::kViewer;
+ return AuthScope::kNone;
+}
+
} // namespace
AuthManager::AuthManager(Config* config,
const std::vector<uint8_t>& certificate_fingerprint)
- : config_{config}, certificate_fingerprint_{certificate_fingerprint} {
+ : config_{config},
+ certificate_fingerprint_{certificate_fingerprint},
+ access_secret_{CreateSecret()} {
if (config_) {
- SetSecret(config_->GetSettings().secret,
- config_->GetSettings().root_client_token_owner);
+ SetAuthSecret(config_->GetSettings().secret,
+ config_->GetSettings().root_client_token_owner);
} else {
- SetSecret({}, RootClientTokenOwner::kNone);
+ SetAuthSecret({}, RootClientTokenOwner::kNone);
}
}
-AuthManager::AuthManager(const std::vector<uint8_t>& secret,
+AuthManager::AuthManager(const std::vector<uint8_t>& auth_secret,
const std::vector<uint8_t>& certificate_fingerprint,
+ const std::vector<uint8_t>& access_secret,
base::Clock* clock)
: AuthManager(nullptr, certificate_fingerprint) {
- SetSecret(secret, RootClientTokenOwner::kNone);
+ access_secret_ = access_secret.size() == kSha256OutputSize ? access_secret
+ : CreateSecret();
+ SetAuthSecret(auth_secret, RootClientTokenOwner::kNone);
if (clock)
clock_ = clock;
}
-void AuthManager::SetSecret(const std::vector<uint8_t>& secret,
- RootClientTokenOwner owner) {
- secret_ = secret;
+void AuthManager::SetAuthSecret(const std::vector<uint8_t>& secret,
+ RootClientTokenOwner owner) {
+ auth_secret_ = secret;
- if (secret.size() != kSha256OutputSize) {
- secret_ = CreateSecret();
+ if (auth_secret_.size() != kSha256OutputSize) {
+ auth_secret_ = CreateSecret();
owner = RootClientTokenOwner::kNone;
}
- if (!config_ || (config_->GetSettings().secret == secret_ &&
+ if (!config_ || (config_->GetSettings().secret == auth_secret_ &&
config_->GetSettings().root_client_token_owner == owner)) {
return;
}
@@ -129,25 +243,61 @@
AuthManager::~AuthManager() {}
-// Returns "[hmac]scope:id:time".
-std::vector<uint8_t> AuthManager::CreateAccessToken(const UserInfo& user_info) {
- std::string data_str{CreateTokenData(user_info, Now())};
- std::vector<uint8_t> data{data_str.begin(), data_str.end()};
- std::vector<uint8_t> hash{HmacSha256(secret_, data)};
- hash.insert(hash.end(), data.begin(), data.end());
- return hash;
+std::vector<uint8_t> AuthManager::CreateAccessToken(const UserInfo& user_info,
+ base::TimeDelta ttl) const {
+ Caveat scope{kUwMacaroonCaveatTypeScope, ToMacaroonScope(user_info.scope())};
+ Caveat user{kUwMacaroonCaveatTypeIdentifier, user_info.user_id()};
+ Caveat issued{kUwMacaroonCaveatTypeExpiration,
+ static_cast<uint32_t>((Now() + ttl).ToTimeT())};
+ return CreateMacaroonToken(
+ access_secret_,
+ {
+ scope.GetCaveat(), user.GetCaveat(), issued.GetCaveat(),
+ });
}
-// Parses "base64([hmac]scope:id:time)".
-UserInfo AuthManager::ParseAccessToken(const std::vector<uint8_t>& token,
- base::Time* time) const {
- if (token.size() <= kSha256OutputSize)
- return UserInfo{};
- std::vector<uint8_t> hash(token.begin(), token.begin() + kSha256OutputSize);
- std::vector<uint8_t> data(token.begin() + kSha256OutputSize, token.end());
- if (hash != HmacSha256(secret_, data))
- return UserInfo{};
- return SplitTokenData(std::string(data.begin(), data.end()), time);
+bool AuthManager::ParseAccessToken(const std::vector<uint8_t>& token,
+ UserInfo* user_info,
+ ErrorPtr* error) const {
+ std::vector<uint8_t> buffer;
+ UwMacaroon macaroon{};
+
+ uint32_t scope{0};
+ std::string user_id;
+ uint32_t expiration{0};
+
+ if (!LoadMacaroon(token, &buffer, &macaroon, error) ||
+ !VerifyMacaroon(access_secret_, macaroon, error) ||
+ macaroon.num_caveats != 3 ||
+ !ReadCaveat(macaroon.caveats[0], kUwMacaroonCaveatTypeScope, &scope,
+ error) ||
+ !ReadCaveat(macaroon.caveats[1], kUwMacaroonCaveatTypeIdentifier,
+ &user_id, error) ||
+ !ReadCaveat(macaroon.caveats[2], kUwMacaroonCaveatTypeExpiration,
+ &expiration, error)) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain,
+ errors::kInvalidAuthorization, "Invalid token");
+ return false;
+ }
+
+ AuthScope auth_scope{FromMacaroonScope(scope)};
+ if (auth_scope == AuthScope::kNone) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain,
+ errors::kInvalidAuthorization, "Invalid token data");
+ return false;
+ }
+
+ base::Time time{base::Time::FromTimeT(expiration)};
+ if (time < clock_->Now()) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain,
+ errors::kAuthorizationExpired, "Token is expired");
+ return false;
+ }
+
+ if (user_info)
+ *user_info = UserInfo{auth_scope, user_id};
+
+ return true;
}
std::vector<uint8_t> AuthManager::ClaimRootClientAuthToken(
@@ -174,13 +324,13 @@
bool AuthManager::ConfirmClientAuthToken(const std::vector<uint8_t>& token,
ErrorPtr* error) {
// Cover case when caller sent confirm twice.
- if (pending_claims_.empty() && IsValidAuthToken(token))
- return true;
+ if (pending_claims_.empty())
+ return IsValidAuthToken(token, error);
auto claim =
std::find_if(pending_claims_.begin(), pending_claims_.end(),
[&token](const decltype(pending_claims_)::value_type& auth) {
- return auth.first->IsValidAuthToken(token);
+ return auth.first->IsValidAuthToken(token, nullptr);
});
if (claim == pending_claims_.end()) {
Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kNotFound,
@@ -188,7 +338,7 @@
return false;
}
- SetSecret(claim->first->GetSecret(), claim->second);
+ SetAuthSecret(claim->first->GetAuthSecret(), claim->second);
pending_claims_.clear();
return true;
}
@@ -197,37 +347,64 @@
Caveat scope{kUwMacaroonCaveatTypeScope, kUwMacaroonCaveatScopeTypeOwner};
Caveat issued{kUwMacaroonCaveatTypeIssued,
static_cast<uint32_t>(Now().ToTimeT())};
-
- UwMacaroonCaveat caveats[] = {
- 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)));
-
- std::vector<uint8_t> token(kMaxMacaroonSize);
- size_t len = 0;
- CHECK(uw_macaroon_dump_(&macaroon, token.data(), token.size(), &len));
- token.resize(len);
- return token;
+ return CreateMacaroonToken(auth_secret_,
+ {
+ scope.GetCaveat(), issued.GetCaveat(),
+ });
}
base::Time AuthManager::Now() const {
return clock_->Now();
}
-bool AuthManager::IsValidAuthToken(const std::vector<uint8_t>& token) const {
- std::vector<uint8_t> buffer(kMaxMacaroonSize);
+bool AuthManager::IsValidAuthToken(const std::vector<uint8_t>& token,
+ ErrorPtr* error) const {
+ std::vector<uint8_t> buffer;
UwMacaroon macaroon{};
- if (!uw_macaroon_load_(token.data(), token.size(), buffer.data(),
- buffer.size(), &macaroon)) {
+ if (!LoadMacaroon(token, &buffer, &macaroon, error) ||
+ !VerifyMacaroon(auth_secret_, macaroon, error)) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidAuthCode,
+ "Invalid token");
return false;
}
+ return true;
+}
- CHECK_EQ(kSha256OutputSize, secret_.size());
- return uw_macaroon_verify_(&macaroon, secret_.data(), secret_.size());
+bool AuthManager::CreateAccessTokenFromAuth(
+ const std::vector<uint8_t>& auth_token,
+ base::TimeDelta ttl,
+ std::vector<uint8_t>* access_token,
+ AuthScope* access_token_scope,
+ base::TimeDelta* access_token_ttl,
+ ErrorPtr* error) const {
+ // TODO(vitalybuka): implement token validation.
+ if (!IsValidAuthToken(auth_token, error))
+ return false;
+
+ if (!access_token)
+ return true;
+
+ // TODO(vitalybuka): User and scope must be parsed from auth_token.
+ UserInfo info{config_ ? config_->GetSettings().local_anonymous_access_role
+ : AuthScope::kViewer,
+ base::GenerateGUID()};
+
+ // TODO(vitalybuka): TTL also should be reduced in accordance with auth_token.
+ *access_token = CreateAccessToken(info, ttl);
+
+ if (access_token_scope)
+ *access_token_scope = info.scope();
+
+ if (access_token_ttl)
+ *access_token_ttl = ttl;
+ return true;
+}
+
+std::vector<uint8_t> AuthManager::CreateSessionId() {
+ std::vector<uint8_t> result;
+ AppendToArray(Now().ToTimeT(), &result);
+ AppendToArray(++session_counter_, &result);
+ return result;
}
} // namespace privet
diff --git a/src/privet/auth_manager.h b/src/privet/auth_manager.h
index 44b8bca..309d80e 100644
--- a/src/privet/auth_manager.h
+++ b/src/privet/auth_manager.h
@@ -28,16 +28,21 @@
const std::vector<uint8_t>& certificate_fingerprint);
// Constructor for tests.
- AuthManager(const std::vector<uint8_t>& secret,
+ AuthManager(const std::vector<uint8_t>& auth_secret,
const std::vector<uint8_t>& certificate_fingerprint,
+ const std::vector<uint8_t>& access_secret,
base::Clock* clock = nullptr);
~AuthManager();
- std::vector<uint8_t> CreateAccessToken(const UserInfo& user_info);
- UserInfo ParseAccessToken(const std::vector<uint8_t>& token,
- base::Time* time) const;
+ std::vector<uint8_t> CreateAccessToken(const UserInfo& user_info,
+ base::TimeDelta ttl) const;
- const std::vector<uint8_t>& GetSecret() const { return secret_; }
+ bool ParseAccessToken(const std::vector<uint8_t>& token,
+ UserInfo* user_info,
+ ErrorPtr* error) const;
+
+ const std::vector<uint8_t>& GetAuthSecret() const { return auth_secret_; }
+ const std::vector<uint8_t>& GetAccessSecret() const { return access_secret_; }
const std::vector<uint8_t>& GetCertificateFingerprint() const {
return certificate_fingerprint_;
}
@@ -50,18 +55,29 @@
ErrorPtr* error);
std::vector<uint8_t> GetRootClientAuthToken() const;
- bool IsValidAuthToken(const std::vector<uint8_t>& token) const;
+ bool IsValidAuthToken(const std::vector<uint8_t>& token,
+ ErrorPtr* error) const;
+ bool CreateAccessTokenFromAuth(const std::vector<uint8_t>& auth_token,
+ base::TimeDelta ttl,
+ std::vector<uint8_t>* access_token,
+ AuthScope* access_token_scope,
+ base::TimeDelta* access_token_ttl,
+ ErrorPtr* error) const;
- void SetSecret(const std::vector<uint8_t>& secret,
- RootClientTokenOwner owner);
+ void SetAuthSecret(const std::vector<uint8_t>& secret,
+ RootClientTokenOwner owner);
+
+ std::vector<uint8_t> CreateSessionId();
private:
- Config* config_{nullptr};
+ Config* config_{nullptr}; // Can be nullptr for tests.
base::DefaultClock default_clock_;
base::Clock* clock_{&default_clock_};
+ uint32_t session_counter_{0};
- std::vector<uint8_t> secret_;
+ std::vector<uint8_t> auth_secret_; // Persistent.
std::vector<uint8_t> certificate_fingerprint_;
+ std::vector<uint8_t> access_secret_; // New on every reboot.
std::deque<std::pair<std::unique_ptr<AuthManager>, RootClientTokenOwner>>
pending_claims_;
diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc
index a571d4f..70750ad 100644
--- a/src/privet/auth_manager_unittest.cc
+++ b/src/privet/auth_manager_unittest.cc
@@ -20,7 +20,8 @@
class AuthManagerTest : public testing::Test {
public:
void SetUp() override {
- EXPECT_GE(auth_.GetSecret().size(), 32u);
+ EXPECT_GE(auth_.GetAuthSecret().size(), 32u);
+ EXPECT_GE(auth_.GetAccessSecret().size(), 32u);
EXPECT_GE(auth_.GetCertificateFingerprint().size(), 32u);
EXPECT_CALL(clock_, Now())
@@ -28,131 +29,146 @@
}
protected:
- const std::vector<uint8_t> kSecret{69, 53, 17, 37, 80, 73, 2, 5, 79, 64, 41,
- 57, 12, 54, 65, 63, 72, 74, 93, 81, 20, 95,
- 89, 3, 94, 92, 27, 21, 49, 90, 36, 6};
- const std::vector<uint8_t> kSecret2{
+ const std::vector<uint8_t> kSecret1{
78, 40, 39, 68, 29, 19, 70, 86, 38, 61, 13, 55, 33, 32, 51, 52,
34, 43, 97, 48, 8, 56, 11, 99, 50, 59, 24, 26, 31, 71, 76, 28};
+ const std::vector<uint8_t> kSecret2{
+ 69, 53, 17, 37, 80, 73, 2, 5, 79, 64, 41, 57, 12, 54, 65, 63,
+ 72, 74, 93, 81, 20, 95, 89, 3, 94, 92, 27, 21, 49, 90, 36, 6};
const std::vector<uint8_t> kFingerprint{
22, 47, 23, 77, 42, 98, 96, 25, 83, 16, 9, 14, 91, 44, 15, 75,
60, 62, 10, 18, 82, 35, 88, 100, 30, 45, 7, 46, 67, 84, 58, 85};
test::MockClock clock_;
- AuthManager auth_{kSecret, kFingerprint, &clock_};
+ AuthManager auth_{kSecret1, kFingerprint, kSecret2, &clock_};
};
TEST_F(AuthManagerTest, RandomSecret) {
- EXPECT_GE(auth_.GetSecret().size(), 32u);
+ AuthManager auth{{}, {}, {}, &clock_};
+ EXPECT_EQ(auth.GetAuthSecret().size(), 32u);
+ EXPECT_EQ(auth.GetAccessSecret().size(), 32u);
}
TEST_F(AuthManagerTest, DifferentSecret) {
- AuthManager auth{kSecret2, {}};
- EXPECT_GE(auth.GetSecret().size(), 32u);
- EXPECT_NE(auth_.GetSecret(), auth.GetSecret());
+ AuthManager auth{kSecret2, {}, kSecret1};
+ EXPECT_EQ(auth.GetAuthSecret().size(), 32u);
+ EXPECT_EQ(auth.GetAccessSecret().size(), 32u);
+ EXPECT_NE(auth_.GetAccessSecret(), auth.GetAccessSecret());
+ EXPECT_NE(auth_.GetAuthSecret(), auth.GetAuthSecret());
}
TEST_F(AuthManagerTest, Constructor) {
- EXPECT_EQ(kSecret, auth_.GetSecret());
+ EXPECT_EQ(kSecret1, auth_.GetAuthSecret());
+ EXPECT_EQ(kSecret2, auth_.GetAccessSecret());
EXPECT_EQ(kFingerprint, auth_.GetCertificateFingerprint());
}
TEST_F(AuthManagerTest, CreateAccessToken) {
- EXPECT_EQ(
- "OUH2L2npY+Gzwjf9AnqigGSK3hxIVR+xX8/Cnu4DGf8wOjA6MTQxMDAwMDAwMA==",
- Base64Encode(auth_.CreateAccessToken(UserInfo{AuthScope::kNone, 123})));
- EXPECT_EQ(
- "iZx0qgEHFF5lq+Q503GtgU0d6gLQ9TlLsU+DcFbZb2QxOjIzNDoxNDEwMDAwMDAw",
- Base64Encode(auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, 234})));
- EXPECT_EQ("cWkAHxSBYtTFV3Va/9mcynR8iFZo2qr+8+WewmumF74zOjI1NzoxNDEwMDAwMDAw",
- Base64Encode(
- auth_.CreateAccessToken(UserInfo{AuthScope::kManager, 257})));
- EXPECT_EQ(
- "s3GnCThkQXIzGQoPDlJoiehQiJ5yy4SYUVQzMN2kY0o0OjQ1NjoxNDEwMDAwMDAw",
- Base64Encode(auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456})));
+ EXPECT_EQ("UABRUHgcSZDry0bvIsoJv+WDQgEURQJjMjM0RgUaVArkgA==",
+ Base64Encode(auth_.CreateAccessToken(
+ UserInfo{AuthScope::kViewer, "234"}, {})));
+ EXPECT_EQ("UL7YEruLg5QQRDIp2+u1cqCDQgEIRQJjMjU3RgUaVArkgA==",
+ Base64Encode(auth_.CreateAccessToken(
+ UserInfo{AuthScope::kManager, "257"}, {})));
+ EXPECT_EQ("UPFGeZRanR1wLGYLP5ZDkXiDQgECRQJjNDU2RgUaVArkgA==",
+ Base64Encode(auth_.CreateAccessToken(
+ UserInfo{AuthScope::kOwner, "456"}, {})));
auto new_time = clock_.Now() + base::TimeDelta::FromDays(11);
EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time));
- EXPECT_EQ(
- "qAmlJykiPTnFljfOKSf3BUII9YZG8/ttzD76q+fII1YyOjM0NToxNDEwOTUwNDAw",
- Base64Encode(auth_.CreateAccessToken(UserInfo{AuthScope::kUser, 345})));
+ EXPECT_EQ("UMm9KlF3OEtZFBmhScJpl4uDQgEORQJjMzQ1RgUaVBllAA==",
+ Base64Encode(auth_.CreateAccessToken(
+ UserInfo{AuthScope::kUser, "345"}, {})));
}
TEST_F(AuthManagerTest, CreateSameToken) {
- EXPECT_EQ(auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}),
- auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}));
+ EXPECT_EQ(auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, "555"}, {}),
+ auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, "555"}, {}));
}
TEST_F(AuthManagerTest, CreateTokenDifferentScope) {
- EXPECT_NE(auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, 456}),
- auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}));
+ EXPECT_NE(auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, "456"}, {}),
+ auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "456"}, {}));
}
TEST_F(AuthManagerTest, CreateTokenDifferentUser) {
- EXPECT_NE(auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}),
- auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, 789}));
+ EXPECT_NE(auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "456"}, {}),
+ auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "789"}, {}));
}
TEST_F(AuthManagerTest, CreateTokenDifferentTime) {
- auto token = auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567});
+ auto token = auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "567"}, {});
EXPECT_CALL(clock_, Now())
.WillRepeatedly(Return(base::Time::FromTimeT(1400000000)));
- EXPECT_NE(token, auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567}));
+ EXPECT_NE(token,
+ auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "567"}, {}));
}
TEST_F(AuthManagerTest, CreateTokenDifferentInstance) {
- EXPECT_NE(
- auth_.CreateAccessToken(UserInfo{AuthScope::kUser, 123}),
- AuthManager({}, {}).CreateAccessToken(UserInfo{AuthScope::kUser, 123}));
+ EXPECT_NE(auth_.CreateAccessToken(UserInfo{AuthScope::kUser, "123"}, {}),
+ AuthManager({}, {}).CreateAccessToken(
+ UserInfo{AuthScope::kUser, "123"}, {}));
}
TEST_F(AuthManagerTest, ParseAccessToken) {
// Multiple attempts with random secrets.
+ const auto kStartTime = base::Time::FromTimeT(1412121212);
for (size_t i = 0; i < 1000; ++i) {
- AuthManager auth{{}, {}, &clock_};
+ EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(kStartTime));
- 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.
- EXPECT_GE(1, std::abs((clock_.Now() - time2).InSeconds()));
+ AuthManager auth{{}, {}, {}, &clock_};
+
+ auto token = auth.CreateAccessToken(UserInfo{AuthScope::kUser, "5"},
+ base::TimeDelta::FromSeconds(i));
+ UserInfo user_info;
+ EXPECT_FALSE(auth_.ParseAccessToken(token, &user_info, nullptr));
+ EXPECT_TRUE(auth.ParseAccessToken(token, &user_info, nullptr));
+ EXPECT_EQ(AuthScope::kUser, user_info.scope());
+ EXPECT_EQ("5", user_info.user_id());
+
+ EXPECT_CALL(clock_, Now())
+ .WillRepeatedly(Return(kStartTime + base::TimeDelta::FromSeconds(i)));
+ EXPECT_TRUE(auth.ParseAccessToken(token, &user_info, nullptr));
+
+ EXPECT_CALL(clock_, Now())
+ .WillRepeatedly(
+ Return(kStartTime + base::TimeDelta::FromSeconds(i + 1)));
+ EXPECT_FALSE(auth.ParseAccessToken(token, &user_info, nullptr));
}
}
TEST_F(AuthManagerTest, GetRootClientAuthToken) {
- EXPECT_EQ("UFTBUcgd9d0HnPRnLeroN2mCQgECRgMaVArkgA==",
+ EXPECT_EQ("UK1ACOc3cWGjGBoTIX2bd3qCQgECRgMaVArkgA==",
Base64Encode(auth_.GetRootClientAuthToken()));
}
TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentTime) {
auto new_time = clock_.Now() + base::TimeDelta::FromDays(15);
EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time));
- EXPECT_EQ("UGKqwMYGQNOd8jeYFDOsM02CQgECRgMaVB6rAA==",
+ EXPECT_EQ("UBpNF8g/GbNUmAyHg1qqJr+CQgECRgMaVB6rAA==",
Base64Encode(auth_.GetRootClientAuthToken()));
}
TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentSecret) {
- AuthManager auth{kSecret2, {}, &clock_};
- EXPECT_EQ("UK1ACOc3cWGjGBoTIX2bd3qCQgECRgMaVArkgA==",
+ AuthManager auth{kSecret2, {}, kSecret1, &clock_};
+ EXPECT_EQ("UFTBUcgd9d0HnPRnLeroN2mCQgECRgMaVArkgA==",
Base64Encode(auth.GetRootClientAuthToken()));
}
TEST_F(AuthManagerTest, IsValidAuthToken) {
- EXPECT_TRUE(auth_.IsValidAuthToken(auth_.GetRootClientAuthToken()));
+ EXPECT_TRUE(auth_.IsValidAuthToken(auth_.GetRootClientAuthToken(), nullptr));
// Multiple attempts with random secrets.
for (size_t i = 0; i < 1000; ++i) {
- AuthManager auth{{}, {}, &clock_};
+ AuthManager auth{{}, {}, {}, &clock_};
auto token = auth.GetRootClientAuthToken();
- EXPECT_FALSE(auth_.IsValidAuthToken(token));
- EXPECT_TRUE(auth.IsValidAuthToken(token));
+ EXPECT_FALSE(auth_.IsValidAuthToken(token, nullptr));
+ EXPECT_TRUE(auth.IsValidAuthToken(token, nullptr));
}
}
class AuthManagerClaimTest : public testing::Test {
public:
- void SetUp() override { EXPECT_GE(auth_.GetSecret().size(), 32u); }
+ void SetUp() override { EXPECT_EQ(auth_.GetAuthSecret().size(), 32u); }
bool TestClaim(RootClientTokenOwner owner, RootClientTokenOwner claimer) {
Config::Transaction change{&config_};
@@ -191,12 +207,12 @@
TEST_F(AuthManagerClaimTest, NormalClaim) {
auto token =
auth_.ClaimRootClientAuthToken(RootClientTokenOwner::kCloud, nullptr);
- EXPECT_FALSE(auth_.IsValidAuthToken(token));
+ EXPECT_FALSE(auth_.IsValidAuthToken(token, nullptr));
EXPECT_EQ(RootClientTokenOwner::kNone,
config_.GetSettings().root_client_token_owner);
EXPECT_TRUE(auth_.ConfirmClientAuthToken(token, nullptr));
- EXPECT_TRUE(auth_.IsValidAuthToken(token));
+ EXPECT_TRUE(auth_.IsValidAuthToken(token, nullptr));
EXPECT_EQ(RootClientTokenOwner::kCloud,
config_.GetSettings().root_client_token_owner);
}
@@ -225,5 +241,18 @@
EXPECT_FALSE(auth_.ConfirmClientAuthToken(token, nullptr));
}
+TEST_F(AuthManagerClaimTest, CreateAccessTokenFromAuth) {
+ std::vector<uint8_t> access_token;
+ AuthScope scope;
+ base::TimeDelta ttl;
+ EXPECT_TRUE(auth_.CreateAccessTokenFromAuth(
+ auth_.GetRootClientAuthToken(), base::TimeDelta::FromDays(1),
+ &access_token, &scope, &ttl, nullptr));
+ UserInfo user_info;
+ EXPECT_TRUE(auth_.ParseAccessToken(access_token, &user_info, nullptr));
+ EXPECT_EQ(scope, user_info.scope());
+ EXPECT_FALSE(user_info.user_id().empty());
+}
+
} // namespace privet
} // namespace weave
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc
index ed22c15..4bf47b3 100644
--- a/src/privet/cloud_delegate.cc
+++ b/src/privet/cloud_delegate.cc
@@ -165,7 +165,7 @@
const UserInfo& user_info,
const CommandDoneCallback& callback) override {
CHECK(user_info.scope() != AuthScope::kNone);
- CHECK_NE(user_info.user_id(), 0u);
+ CHECK(!user_info.user_id().empty());
ErrorPtr error;
UserRole role;
@@ -230,8 +230,8 @@
private:
void OnCommandAdded(Command* command) {
- // Set to 0 for any new unknown command.
- command_owners_.insert(std::make_pair(command->GetID(), 0));
+ // Set to "" for any new unknown command.
+ command_owners_.insert(std::make_pair(command->GetID(), ""));
}
void OnCommandRemoved(Command* command) {
@@ -310,11 +310,11 @@
return command;
}
- bool CanAccessCommand(uint64_t owner_id,
+ bool CanAccessCommand(const std::string& owner_id,
const UserInfo& user_info,
ErrorPtr* error) const {
CHECK(user_info.scope() != AuthScope::kNone);
- CHECK_NE(user_info.user_id(), 0u);
+ CHECK(!user_info.user_id().empty());
if (user_info.scope() == AuthScope::kManager ||
owner_id == user_info.user_id()) {
@@ -343,7 +343,7 @@
int registation_retry_count_{0};
// Map of command IDs to user IDs.
- std::map<std::string, uint64_t> command_owners_;
+ std::map<std::string, std::string> command_owners_;
// Backoff entry for retrying device registration.
BackoffEntry backoff_entry_{®ister_backoff_policy};
diff --git a/src/privet/mock_delegates.h b/src/privet/mock_delegates.h
index de94fe9..c75d438 100644
--- a/src/privet/mock_delegates.h
+++ b/src/privet/mock_delegates.h
@@ -62,14 +62,21 @@
class MockSecurityDelegate : public SecurityDelegate {
public:
- MOCK_METHOD1(CreateAccessToken, std::string(const UserInfo&));
- MOCK_CONST_METHOD2(ParseAccessToken,
- UserInfo(const std::string&, base::Time*));
+ MOCK_METHOD7(CreateAccessToken,
+ bool(AuthType,
+ const std::string&,
+ AuthScope,
+ std::string*,
+ AuthScope*,
+ base::TimeDelta*,
+ ErrorPtr*));
+ MOCK_CONST_METHOD3(ParseAccessToken,
+ bool(const std::string&, UserInfo*, ErrorPtr*));
MOCK_CONST_METHOD0(GetPairingTypes, std::set<PairingType>());
MOCK_CONST_METHOD0(GetCryptoTypes, std::set<CryptoType>());
+ MOCK_CONST_METHOD0(GetAuthTypes, std::set<AuthType>());
MOCK_METHOD1(ClaimRootClientAuthToken, std::string(ErrorPtr*));
MOCK_METHOD2(ConfirmClientAuthToken, bool(const std::string&, ErrorPtr*));
- MOCK_CONST_METHOD1(IsValidPairingCode, bool(const std::string&));
MOCK_METHOD5(
StartPairing,
bool(PairingType, CryptoType, std::string*, std::string*, ErrorPtr*));
@@ -80,10 +87,14 @@
std::string*,
ErrorPtr*));
MOCK_METHOD2(CancelPairing, bool(const std::string&, ErrorPtr*));
+ MOCK_METHOD0(CreateSessionId, std::string());
MockSecurityDelegate() {
- EXPECT_CALL(*this, CreateAccessToken(_))
- .WillRepeatedly(Return("GuestAccessToken"));
+ EXPECT_CALL(*this, CreateAccessToken(_, _, _, _, _, _, _))
+ .WillRepeatedly(DoAll(
+ SetArgPointee<3>("GuestAccessToken"),
+ SetArgPointee<4>(AuthScope::kViewer),
+ SetArgPointee<5>(base::TimeDelta::FromSeconds(15)), Return(true)));
EXPECT_CALL(*this, ClaimRootClientAuthToken(_))
.WillRepeatedly(Return("RootClientAuthToken"));
@@ -91,9 +102,10 @@
EXPECT_CALL(*this, ConfirmClientAuthToken("DerivedClientAuthToken", _))
.WillRepeatedly(Return(true));
- EXPECT_CALL(*this, ParseAccessToken(_, _))
- .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time::Now()),
- Return(UserInfo{AuthScope::kViewer, 1234567})));
+ EXPECT_CALL(*this, ParseAccessToken(_, _, _))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<1>(UserInfo{AuthScope::kViewer, "1234567"}),
+ Return(true)));
EXPECT_CALL(*this, GetPairingTypes())
.WillRepeatedly(Return(std::set<PairingType>{
@@ -104,6 +116,10 @@
.WillRepeatedly(Return(std::set<CryptoType>{
CryptoType::kSpake_p224,
}));
+ EXPECT_CALL(*this, GetAuthTypes())
+ .WillRepeatedly(Return(std::set<AuthType>{
+ AuthType::kAnonymous, AuthType::kPairing, AuthType::kLocal,
+ }));
EXPECT_CALL(*this, StartPairing(_, _, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<2>("testSession"),
@@ -114,6 +130,7 @@
.WillRepeatedly(DoAll(SetArgPointee<2>("testFingerprint"),
SetArgPointee<3>("testSignature"), Return(true)));
EXPECT_CALL(*this, CancelPairing(_, _)).WillRepeatedly(Return(true));
+ EXPECT_CALL(*this, CreateSessionId()).WillRepeatedly(Return("SessionId"));
}
};
diff --git a/src/privet/privet_handler.cc b/src/privet/privet_handler.cc
index 57c8633..db55861 100644
--- a/src/privet/privet_handler.cc
+++ b/src/privet/privet_handler.cc
@@ -69,8 +69,8 @@
const char kInfoWifiCapabilitiesKey[] = "capabilities";
const char kInfoWifiSsidKey[] = "ssid";
const char kInfoWifiHostedSsidKey[] = "hostedSsid";
-
const char kInfoTimeKey[] = "time";
+const char kInfoSessionIdKey[] = "sessionId";
const char kPairingKey[] = "pairing";
const char kPairingSessionIdKey[] = "sessionId";
@@ -79,9 +79,6 @@
const char kPairingFingerprintKey[] = "certFingerprint";
const char kPairingSignatureKey[] = "certSignature";
-const char kAuthTypeAnonymousValue[] = "anonymous";
-const char kAuthTypePairingValue[] = "pairing";
-
const char kAuthModeKey[] = "mode";
const char kAuthCodeKey[] = "authCode";
const char kAuthRequestedScopeKey[] = "requestedScope";
@@ -119,12 +116,6 @@
const char kInvalidParamValueFormat[] = "Invalid parameter: '%s'='%s'";
-const int kAccessTokenExpirationSeconds = 3600;
-
-// Threshold to reduce probability of expiration because of clock difference
-// between device and client. Value is just a guess.
-const int kAccessTokenExpirationThresholdSeconds = 300;
-
template <class Container>
std::unique_ptr<base::ListValue> ToValue(const Container& list) {
std::unique_ptr<base::ListValue> value_list(new base::ListValue());
@@ -158,14 +149,6 @@
{errors::kAlreadyClaimed, http::kDenied},
};
-AuthScope AuthScopeFromString(const std::string& scope, AuthScope auto_scope) {
- if (scope == kAuthScopeAutoValue)
- return auto_scope;
- AuthScope scope_id = AuthScope::kNone;
- StringToEnum(scope, &scope_id);
- return scope_id;
-}
-
std::string GetAuthTokenFromAuthHeader(const std::string& auth_header) {
return SplitAtFirst(auth_header, " ", true).second;
}
@@ -270,13 +253,8 @@
auth->Set(kPairingKey, pairing_types.release());
std::unique_ptr<base::ListValue> auth_types(new base::ListValue());
- auth_types->AppendString(kAuthTypeAnonymousValue);
- auth_types->AppendString(kAuthTypePairingValue);
-
- // TODO(vitalybuka): Implement cloud auth.
- // if (cloud.GetConnectionState().IsStatusEqual(ConnectionState::kOnline)) {
- // auth_types->AppendString(kAuthTypeCloudValue);
- // }
+ for (AuthType type : security.GetAuthTypes())
+ auth_types->AppendString(EnumToString(type));
auth->Set(kAuthModeKey, auth_types.release());
std::unique_ptr<base::ListValue> crypto_types(new base::ListValue());
@@ -517,24 +495,9 @@
return ReturnError(*error, callback);
}
UserInfo user_info;
- if (token != kAuthTypeAnonymousValue) {
- base::Time time;
- user_info = security_->ParseAccessToken(token, &time);
- if (user_info.scope() == AuthScope::kNone) {
- Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
- errors::kInvalidAuthorization,
- "Invalid access token: %s", token.c_str());
+ if (token != EnumToString(AuthType::kAnonymous)) {
+ if (!security_->ParseAccessToken(token, &user_info, &error))
return ReturnError(*error, callback);
- }
- time += base::TimeDelta::FromSeconds(kAccessTokenExpirationSeconds);
- time +=
- base::TimeDelta::FromSeconds(kAccessTokenExpirationThresholdSeconds);
- if (time < clock_->Now()) {
- Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
- errors::kAuthorizationExpired, "Token expired: %s",
- token.c_str());
- return ReturnError(*error, callback);
- }
}
if (handler->second.scope > user_info.scope()) {
@@ -607,6 +570,7 @@
output.Set(kGcdKey, CreateGcdSection(*cloud_).release());
output.SetDouble(kInfoTimeKey, clock_->Now().ToJsTime());
+ output.SetString(kInfoSessionIdKey, security_->CreateSessionId());
callback.Run(http::kOk, output);
}
@@ -696,55 +660,58 @@
ErrorPtr error;
std::string auth_code_type;
- input.GetString(kAuthModeKey, &auth_code_type);
-
- std::string auth_code;
- input.GetString(kAuthCodeKey, &auth_code);
-
- AuthScope max_auth_scope = AuthScope::kNone;
- if (auth_code_type == kAuthTypeAnonymousValue) {
- max_auth_scope = GetAnonymousMaxScope(*cloud_, wifi_);
- } else if (auth_code_type == kAuthTypePairingValue) {
- if (!security_->IsValidPairingCode(auth_code)) {
- Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
- errors::kInvalidAuthCode, kInvalidParamValueFormat,
- kAuthCodeKey, auth_code.c_str());
- return ReturnError(*error, callback);
- }
- max_auth_scope = AuthScope::kOwner;
- } else {
+ AuthType auth_type{};
+ if (!input.GetString(kAuthModeKey, &auth_code_type) ||
+ !StringToEnum(auth_code_type, &auth_type)) {
Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
errors::kInvalidAuthMode, kInvalidParamValueFormat,
kAuthModeKey, auth_code_type.c_str());
return ReturnError(*error, callback);
}
+ AuthScope desired_scope = AuthScope::kOwner;
+ AuthScope acceptable_scope = AuthScope::kViewer;
+
std::string requested_scope;
input.GetString(kAuthRequestedScopeKey, &requested_scope);
+ if (requested_scope != kAuthScopeAutoValue) {
+ if (!StringToEnum(requested_scope, &desired_scope)) {
+ Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
+ errors::kInvalidRequestedScope,
+ kInvalidParamValueFormat, kAuthRequestedScopeKey,
+ requested_scope.c_str());
+ return ReturnError(*error, callback);
+ }
+ acceptable_scope = std::max(desired_scope, acceptable_scope);
+ }
- AuthScope requested_auth_scope =
- AuthScopeFromString(requested_scope, max_auth_scope);
- if (requested_auth_scope == AuthScope::kNone) {
- Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
- errors::kInvalidRequestedScope, kInvalidParamValueFormat,
- kAuthRequestedScopeKey, requested_scope.c_str());
+ if (auth_type == AuthType::kAnonymous)
+ desired_scope = GetAnonymousMaxScope(*cloud_, wifi_);
+
+ std::string auth_code;
+ input.GetString(kAuthCodeKey, &auth_code);
+
+ std::string access_token;
+ base::TimeDelta access_token_ttl;
+ AuthScope access_token_scope = AuthScope::kNone;
+ if (!security_->CreateAccessToken(auth_type, auth_code, desired_scope,
+ &access_token, &access_token_scope,
+ &access_token_ttl, &error)) {
return ReturnError(*error, callback);
}
- if (requested_auth_scope > max_auth_scope) {
+ if (access_token_scope < acceptable_scope) {
Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
errors::kAccessDenied, "Scope '%s' is not allowed",
- EnumToString(requested_auth_scope).c_str());
+ EnumToString(access_token_scope).c_str());
return ReturnError(*error, callback);
}
base::DictionaryValue output;
- output.SetString(kAuthAccessTokenKey,
- security_->CreateAccessToken(
- UserInfo{requested_auth_scope, ++last_user_id_}));
+ output.SetString(kAuthAccessTokenKey, access_token);
output.SetString(kAuthTokenTypeKey, kAuthorizationHeaderPrefix);
- output.SetInteger(kAuthExpiresInKey, kAccessTokenExpirationSeconds);
- output.SetString(kAuthScopeKey, EnumToString(requested_auth_scope));
+ output.SetInteger(kAuthExpiresInKey, access_token_ttl.InSeconds());
+ output.SetString(kAuthScopeKey, EnumToString(access_token_scope));
callback.Run(http::kOk, output);
}
diff --git a/src/privet/privet_handler.h b/src/privet/privet_handler.h
index 646cab2..4eba329 100644
--- a/src/privet/privet_handler.h
+++ b/src/privet/privet_handler.h
@@ -162,7 +162,6 @@
std::vector<UpdateRequestParameters> update_requests_;
int last_update_request_id_{0};
- uint64_t last_user_id_{0};
uint64_t state_fingerprint_{1};
uint64_t traits_fingerprint_{1};
uint64_t components_fingerprint_{1};
diff --git a/src/privet/privet_handler_unittest.cc b/src/privet/privet_handler_unittest.cc
index fcbd0d0..6d6a289 100644
--- a/src/privet/privet_handler_unittest.cc
+++ b/src/privet/privet_handler_unittest.cc
@@ -196,9 +196,12 @@
TEST_F(PrivetHandlerTest, ExpiredAuth) {
auth_header_ = "Privet 123";
- EXPECT_CALL(security_, ParseAccessToken(_, _))
- .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time()),
- Return(UserInfo{AuthScope::kOwner, 1})));
+ EXPECT_CALL(security_, ParseAccessToken(_, _, _))
+ .WillRepeatedly(DoAll(WithArgs<2>(Invoke([](ErrorPtr* error) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain,
+ "authorizationExpired", "");
+ })),
+ Return(false)));
EXPECT_PRED2(IsEqualError, CodeWithReason(403, "authorizationExpired"),
HandleRequest("/privet/info", "{}"));
}
@@ -214,6 +217,8 @@
.WillRepeatedly(Return(std::set<PairingType>{}));
EXPECT_CALL(security_, GetCryptoTypes())
.WillRepeatedly(Return(std::set<CryptoType>{}));
+ EXPECT_CALL(security_, GetAuthTypes())
+ .WillRepeatedly(Return(std::set<AuthType>{}));
const char kExpected[] = R"({
'version': '3.0',
@@ -235,8 +240,6 @@
'authentication': {
'anonymousMaxScope': 'user',
'mode': [
- 'anonymous',
- 'pairing'
],
'pairing': [
],
@@ -247,7 +250,8 @@
'id': '',
'status': 'disabled'
},
- 'time': 1410000001000.0
+ 'time': 1410000001000.0,
+ 'sessionId': 'SessionId'
})";
EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/info", "{}"));
}
@@ -286,7 +290,8 @@
'anonymousMaxScope': 'none',
'mode': [
'anonymous',
- 'pairing'
+ 'pairing',
+ 'local'
],
'pairing': [
'pinCode',
@@ -308,7 +313,8 @@
'id': 'TestCloudId',
'status': 'online'
},
- 'time': 1410000001000.0
+ 'time': 1410000001000.0,
+ 'sessionId': 'SessionId'
})";
EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/info", "{}"));
}
@@ -372,8 +378,11 @@
}
TEST_F(PrivetHandlerTest, AuthErrorInvalidAuthCode) {
- EXPECT_CALL(security_, IsValidPairingCode("testToken"))
- .WillRepeatedly(Return(false));
+ auto set_error = [](ErrorPtr* error) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, "invalidAuthCode", "");
+ };
+ EXPECT_CALL(security_, CreateAccessToken(_, "testToken", _, _, _, _, _))
+ .WillRepeatedly(DoAll(WithArgs<6>(Invoke(set_error)), Return(false)));
const char kInput[] = R"({
'mode': 'pairing',
'requestedScope': 'user',
@@ -386,8 +395,8 @@
TEST_F(PrivetHandlerTest, AuthAnonymous) {
const char kExpected[] = R"({
'accessToken': 'GuestAccessToken',
- 'expiresIn': 3600,
- 'scope': 'user',
+ 'expiresIn': 15,
+ 'scope': 'viewer',
'tokenType': 'Privet'
})";
EXPECT_JSON_EQ(kExpected,
@@ -396,10 +405,11 @@
}
TEST_F(PrivetHandlerTest, AuthPairing) {
- EXPECT_CALL(security_, IsValidPairingCode("testToken"))
- .WillRepeatedly(Return(true));
- EXPECT_CALL(security_, CreateAccessToken(_))
- .WillRepeatedly(Return("OwnerAccessToken"));
+ EXPECT_CALL(security_, CreateAccessToken(_, _, _, _, _, _, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>("OwnerAccessToken"),
+ SetArgPointee<4>(AuthScope::kOwner),
+ SetArgPointee<5>(base::TimeDelta::FromSeconds(15)),
+ Return(true)));
const char kInput[] = R"({
'mode': 'pairing',
'requestedScope': 'owner',
@@ -407,21 +417,76 @@
})";
const char kExpected[] = R"({
'accessToken': 'OwnerAccessToken',
- 'expiresIn': 3600,
+ 'expiresIn': 15,
'scope': 'owner',
'tokenType': 'Privet'
})";
EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/auth", kInput));
}
+TEST_F(PrivetHandlerTest, AuthLocalAuto) {
+ EXPECT_CALL(security_, CreateAccessToken(_, _, _, _, _, _, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>("UserAccessToken"),
+ SetArgPointee<4>(AuthScope::kUser),
+ SetArgPointee<5>(base::TimeDelta::FromSeconds(15)),
+ Return(true)));
+ const char kInput[] = R"({
+ 'mode': 'local',
+ 'requestedScope': 'auto',
+ 'authCode': 'localAuthToken'
+ })";
+ const char kExpected[] = R"({
+ 'accessToken': 'UserAccessToken',
+ 'expiresIn': 15,
+ 'scope': 'user',
+ 'tokenType': 'Privet'
+ })";
+ EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/auth", kInput));
+}
+
+TEST_F(PrivetHandlerTest, AuthLocal) {
+ EXPECT_CALL(security_, CreateAccessToken(_, _, _, _, _, _, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>("ManagerAccessToken"),
+ SetArgPointee<4>(AuthScope::kManager),
+ SetArgPointee<5>(base::TimeDelta::FromSeconds(15)),
+ Return(true)));
+ const char kInput[] = R"({
+ 'mode': 'local',
+ 'requestedScope': 'manager',
+ 'authCode': 'localAuthToken'
+ })";
+ const char kExpected[] = R"({
+ 'accessToken': 'ManagerAccessToken',
+ 'expiresIn': 15,
+ 'scope': 'manager',
+ 'tokenType': 'Privet'
+ })";
+ EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/auth", kInput));
+}
+
+TEST_F(PrivetHandlerTest, AuthLocalHighScope) {
+ EXPECT_CALL(security_, CreateAccessToken(_, _, _, _, _, _, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>("UserAccessToken"),
+ SetArgPointee<4>(AuthScope::kUser),
+ SetArgPointee<5>(base::TimeDelta::FromSeconds(1)),
+ Return(true)));
+ const char kInput[] = R"({
+ 'mode': 'local',
+ 'requestedScope': 'manager',
+ 'authCode': 'localAuthToken'
+ })";
+ EXPECT_PRED2(IsEqualError, CodeWithReason(403, "accessDenied"),
+ HandleRequest("/privet/v3/auth", kInput));
+}
+
class PrivetHandlerTestWithAuth : public PrivetHandlerTest {
public:
void SetUp() override {
PrivetHandlerTest::SetUp();
auth_header_ = "Privet 123";
- EXPECT_CALL(security_, ParseAccessToken(_, _))
- .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time::Now()),
- Return(UserInfo{AuthScope::kOwner, 1})));
+ EXPECT_CALL(security_, ParseAccessToken(_, _, _))
+ .WillRepeatedly(DoAll(
+ SetArgPointee<1>(UserInfo{AuthScope::kOwner, "1"}), Return(true)));
}
};
@@ -595,9 +660,9 @@
}
TEST_F(PrivetHandlerSetupTest, GcdSetupAsMaster) {
- EXPECT_CALL(security_, ParseAccessToken(_, _))
- .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time::Now()),
- Return(UserInfo{AuthScope::kManager, 1})));
+ EXPECT_CALL(security_, ParseAccessToken(_, _, _))
+ .WillRepeatedly(DoAll(
+ SetArgPointee<1>(UserInfo{AuthScope::kManager, "1"}), Return(true)));
const char kInput[] = R"({
'gcd': {
'ticketId': 'testTicket',
diff --git a/src/privet/privet_manager.cc b/src/privet/privet_manager.cc
index c3f3885..edc7907 100644
--- a/src/privet/privet_manager.cc
+++ b/src/privet/privet_manager.cc
@@ -62,9 +62,8 @@
CloudDelegate::CreateDefault(task_runner_, device, component_manager);
cloud_observer_.Add(cloud_.get());
- security_.reset(new SecurityManager(
- auth_manager, device->GetSettings().pairing_modes,
- device->GetSettings().embedded_code, disable_security_, task_runner_));
+ security_.reset(new SecurityManager(device->GetMutableConfig(), auth_manager,
+ task_runner_));
network->AddConnectionChangedCallback(
base::Bind(&Manager::OnConnectivityChanged, base::Unretained(this)));
diff --git a/src/privet/privet_types.cc b/src/privet/privet_types.cc
index 7c85937..dd291b3 100644
--- a/src/privet/privet_types.cc
+++ b/src/privet/privet_types.cc
@@ -14,6 +14,7 @@
namespace {
+using privet::AuthType;
using privet::ConnectionState;
using privet::CryptoType;
using privet::SetupState;
@@ -25,6 +26,12 @@
{PairingType::kEmbeddedCode, "embeddedCode"},
};
+const EnumToStringMap<AuthType>::Map kAuthTypeMap[] = {
+ {AuthType::kAnonymous, "anonymous"},
+ {AuthType::kPairing, "pairing"},
+ {AuthType::kLocal, "local"},
+};
+
const EnumToStringMap<ConnectionState::Status>::Map kConnectionStateMap[] = {
{ConnectionState::kDisabled, "disabled"},
{ConnectionState::kUnconfigured, "unconfigured"},
@@ -71,6 +78,10 @@
: EnumToStringMap(kPairingTypeMap) {}
template <>
+LIBWEAVE_EXPORT EnumToStringMap<AuthType>::EnumToStringMap()
+ : EnumToStringMap(kAuthTypeMap) {}
+
+template <>
LIBWEAVE_EXPORT EnumToStringMap<ConnectionState::Status>::EnumToStringMap()
: EnumToStringMap(kConnectionStateMap) {}
diff --git a/src/privet/privet_types.h b/src/privet/privet_types.h
index bc8e400..c738865 100644
--- a/src/privet/privet_types.h
+++ b/src/privet/privet_types.h
@@ -19,6 +19,12 @@
kSpake_p224,
};
+enum class AuthType {
+ kAnonymous,
+ kPairing,
+ kLocal,
+};
+
enum class WifiType {
kWifi24,
kWifi50,
@@ -26,14 +32,15 @@
class UserInfo {
public:
- explicit UserInfo(AuthScope scope = AuthScope::kNone, uint64_t user_id = 0)
- : scope_{scope}, user_id_{scope == AuthScope::kNone ? 0 : user_id} {}
+ explicit UserInfo(AuthScope scope = AuthScope::kNone,
+ const std::string& user_id = {})
+ : scope_{scope}, user_id_{scope == AuthScope::kNone ? "" : user_id} {}
AuthScope scope() const { return scope_; }
- uint64_t user_id() const { return user_id_; }
+ const std::string& user_id() const { return user_id_; }
private:
AuthScope scope_;
- uint64_t user_id_;
+ std::string user_id_;
};
class ConnectionState final {
diff --git a/src/privet/security_delegate.h b/src/privet/security_delegate.h
index 54f957d..867ff2a 100644
--- a/src/privet/security_delegate.h
+++ b/src/privet/security_delegate.h
@@ -22,11 +22,18 @@
virtual ~SecurityDelegate() {}
// Creates access token for the given scope, user id and |time|.
- virtual std::string CreateAccessToken(const UserInfo& user_info) = 0;
+ virtual bool CreateAccessToken(AuthType auth_type,
+ const std::string& auth_code,
+ AuthScope desired_scope,
+ std::string* access_token,
+ AuthScope* granted_scope,
+ base::TimeDelta* ttl,
+ ErrorPtr* error) = 0;
- // Validates |token| and returns scope and user id parsed from that.
- virtual UserInfo ParseAccessToken(const std::string& token,
- base::Time* time) const = 0;
+ // Validates |token| and returns scope, user id parsed from that.
+ virtual bool ParseAccessToken(const std::string& token,
+ UserInfo* user_info,
+ ErrorPtr* error) const = 0;
// Returns list of pairing methods by device.
virtual std::set<PairingType> GetPairingTypes() const = 0;
@@ -34,6 +41,9 @@
// Returns list of crypto methods supported by devices.
virtual std::set<CryptoType> GetCryptoTypes() const = 0;
+ // Returns list of auth methods supported by devices.
+ virtual std::set<AuthType> GetAuthTypes() const = 0;
+
// Returns Root Client Authorization Token.
virtual std::string ClaimRootClientAuthToken(ErrorPtr* error) = 0;
@@ -42,10 +52,6 @@
virtual bool ConfirmClientAuthToken(const std::string& token,
ErrorPtr* error) = 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;
-
virtual bool StartPairing(PairingType mode,
CryptoType crypto,
std::string* session_id,
@@ -60,6 +66,8 @@
virtual bool CancelPairing(const std::string& session_id,
ErrorPtr* error) = 0;
+
+ virtual std::string CreateSessionId() = 0;
};
} // namespace privet
diff --git a/src/privet/security_manager.cc b/src/privet/security_manager.cc
index d2025b1..4cd9276 100644
--- a/src/privet/security_manager.cc
+++ b/src/privet/security_manager.cc
@@ -18,7 +18,6 @@
#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"
@@ -36,6 +35,8 @@
const int kMaxAllowedPairingAttemts = 3;
const int kPairingBlockingTimeMinutes = 1;
+const int kAccessTokenExpirationSeconds = 3600;
+
class Spakep224Exchanger : public SecurityManager::KeyExchanger {
public:
explicit Spakep224Exchanger(const std::string& password)
@@ -88,20 +89,16 @@
} // namespace
-SecurityManager::SecurityManager(AuthManager* auth_manager,
- const std::set<PairingType>& pairing_modes,
- const std::string& embedded_code,
- bool disable_security,
+SecurityManager::SecurityManager(const Config* config,
+ AuthManager* auth_manager,
provider::TaskRunner* task_runner)
- : auth_manager_{auth_manager},
- is_security_disabled_(disable_security),
- pairing_modes_(pairing_modes),
- embedded_code_(embedded_code),
- task_runner_{task_runner} {
+ : config_{config}, auth_manager_{auth_manager}, task_runner_{task_runner} {
CHECK(auth_manager_);
- CHECK_EQ(embedded_code_.empty(),
- std::find(pairing_modes_.begin(), pairing_modes_.end(),
- PairingType::kEmbeddedCode) == pairing_modes_.end());
+ CHECK_EQ(GetSettings().embedded_code.empty(),
+ std::find(GetSettings().pairing_modes.begin(),
+ GetSettings().pairing_modes.end(),
+ PairingType::kEmbeddedCode) ==
+ GetSettings().pairing_modes.end());
}
SecurityManager::~SecurityManager() {
@@ -109,32 +106,140 @@
ClosePendingSession(pending_sessions_.begin()->first);
}
-// Returns "base64([hmac]scope:id:time)".
-std::string SecurityManager::CreateAccessToken(const UserInfo& user_info) {
- return Base64Encode(auth_manager_->CreateAccessToken(user_info));
+bool SecurityManager::CreateAccessTokenImpl(AuthType auth_type,
+ AuthScope desired_scope,
+ std::vector<uint8_t>* access_token,
+ AuthScope* access_token_scope,
+ base::TimeDelta* access_token_ttl) {
+ UserInfo user_info{desired_scope,
+ std::to_string(static_cast<int>(auth_type)) + "/" +
+ std::to_string(++last_user_id_)};
+
+ const base::TimeDelta kTtl =
+ base::TimeDelta::FromSeconds(kAccessTokenExpirationSeconds);
+
+ if (access_token)
+ *access_token = auth_manager_->CreateAccessToken(user_info, kTtl);
+
+ if (access_token_scope)
+ *access_token_scope = user_info.scope();
+
+ if (access_token_ttl)
+ *access_token_ttl = kTtl;
+
+ return true;
}
-// Parses "base64([hmac]scope:id:time)".
-UserInfo SecurityManager::ParseAccessToken(const std::string& token,
- base::Time* time) const {
- std::vector<uint8_t> decoded;
- if (!Base64Decode(token, &decoded))
- return UserInfo{};
+bool SecurityManager::CreateAccessTokenImpl(
+ AuthType auth_type,
+ const std::vector<uint8_t>& auth_code,
+ AuthScope desired_scope,
+ std::vector<uint8_t>* access_token,
+ AuthScope* access_token_scope,
+ base::TimeDelta* access_token_ttl,
+ ErrorPtr* error) {
+ auto disabled_mode = [](ErrorPtr* error) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidAuthMode,
+ "Mode is not available");
+ return false;
+ };
- return auth_manager_->ParseAccessToken(decoded, time);
+ switch (auth_type) {
+ case AuthType::kAnonymous:
+ if (!IsAnonymousAuthSupported())
+ return disabled_mode(error);
+ return CreateAccessTokenImpl(auth_type, desired_scope, access_token,
+ access_token_scope, access_token_ttl);
+ case AuthType::kPairing:
+ if (!IsPairingAuthSupported())
+ return disabled_mode(error);
+ if (!IsValidPairingCode(auth_code)) {
+ Error::AddTo(error, FROM_HERE, errors::kDomain,
+ errors::kInvalidAuthCode, "Invalid authCode");
+ return false;
+ }
+ return CreateAccessTokenImpl(auth_type, desired_scope, access_token,
+ access_token_scope, access_token_ttl);
+ case AuthType::kLocal:
+ if (!IsLocalAuthSupported())
+ return disabled_mode(error);
+ const base::TimeDelta kTtl =
+ base::TimeDelta::FromSeconds(kAccessTokenExpirationSeconds);
+ return auth_manager_->CreateAccessTokenFromAuth(
+ auth_code, kTtl, access_token, access_token_scope, access_token_ttl,
+ error);
+ }
+
+ Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidAuthMode,
+ "Unsupported auth mode");
+ return false;
+}
+
+bool SecurityManager::CreateAccessToken(AuthType auth_type,
+ const std::string& auth_code,
+ AuthScope desired_scope,
+ std::string* access_token,
+ AuthScope* access_token_scope,
+ base::TimeDelta* access_token_ttl,
+ ErrorPtr* error) {
+ std::vector<uint8_t> auth_decoded;
+ if (auth_type != AuthType::kAnonymous &&
+ !Base64Decode(auth_code, &auth_decoded)) {
+ return false;
+ }
+
+ std::vector<uint8_t> access_token_decoded;
+ if (!CreateAccessTokenImpl(auth_type, auth_decoded, desired_scope,
+ &access_token_decoded, access_token_scope,
+ access_token_ttl, error)) {
+ return false;
+ }
+
+ if (access_token)
+ *access_token = Base64Encode(access_token_decoded);
+
+ return true;
+}
+
+bool SecurityManager::ParseAccessToken(const std::string& token,
+ UserInfo* user_info,
+ ErrorPtr* error) const {
+ std::vector<uint8_t> decoded;
+ if (!Base64Decode(token, &decoded)) {
+ Error::AddToPrintf(error, FROM_HERE, errors::kDomain,
+ errors::kInvalidAuthorization,
+ "Invalid token encoding: %s", token.c_str());
+ return false;
+ }
+
+ return auth_manager_->ParseAccessToken(decoded, user_info, error);
}
std::set<PairingType> SecurityManager::GetPairingTypes() const {
- return pairing_modes_;
+ return GetSettings().pairing_modes;
}
std::set<CryptoType> SecurityManager::GetCryptoTypes() const {
std::set<CryptoType> result{CryptoType::kSpake_p224};
- if (is_security_disabled_)
+ if (GetSettings().disable_security)
result.insert(CryptoType::kNone);
return result;
}
+std::set<AuthType> SecurityManager::GetAuthTypes() const {
+ std::set<AuthType> result;
+ if (IsAnonymousAuthSupported())
+ result.insert(AuthType::kAnonymous);
+
+ if (IsPairingAuthSupported())
+ result.insert(AuthType::kPairing);
+
+ if (IsLocalAuthSupported())
+ result.insert(AuthType::kLocal);
+
+ return result;
+}
+
std::string SecurityManager::ClaimRootClientAuthToken(ErrorPtr* error) {
return Base64Encode(auth_manager_->ClaimRootClientAuthToken(
RootClientTokenOwner::kClient, error));
@@ -152,18 +257,19 @@
return auth_manager_->ConfirmClientAuthToken(token_decoded, error);
}
-bool SecurityManager::IsValidPairingCode(const std::string& auth_code) const {
- if (is_security_disabled_)
+const Config::Settings& SecurityManager::GetSettings() const {
+ return config_->GetSettings();
+}
+
+bool SecurityManager::IsValidPairingCode(
+ const std::vector<uint8_t>& auth_code) const {
+ if (GetSettings().disable_security)
return true;
- std::vector<uint8_t> auth_decoded;
- if (!Base64Decode(auth_code, &auth_decoded))
- return false;
for (const auto& session : confirmed_sessions_) {
const std::string& key = session.second->GetKey();
const std::string& id = session.first;
- if (auth_decoded ==
- HmacSha256(std::vector<uint8_t>(key.begin(), key.end()),
- std::vector<uint8_t>(id.begin(), id.end()))) {
+ if (auth_code == HmacSha256(std::vector<uint8_t>(key.begin(), key.end()),
+ std::vector<uint8_t>(id.begin(), id.end()))) {
pairing_attemts_ = 0;
block_pairing_until_ = base::Time{};
return true;
@@ -181,8 +287,9 @@
if (!CheckIfPairingAllowed(error))
return false;
- if (std::find(pairing_modes_.begin(), pairing_modes_.end(), mode) ==
- pairing_modes_.end()) {
+ const auto& pairing_modes = GetSettings().pairing_modes;
+ if (std::find(pairing_modes.begin(), pairing_modes.end(), mode) ==
+ pairing_modes.end()) {
Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
"Pairing mode is not enabled");
return false;
@@ -191,8 +298,8 @@
std::string code;
switch (mode) {
case PairingType::kEmbeddedCode:
- CHECK(!embedded_code_.empty());
- code = embedded_code_;
+ CHECK(!GetSettings().embedded_code.empty());
+ code = GetSettings().embedded_code;
break;
case PairingType::kPinCode:
code = base::StringPrintf("%04i", base::RandInt(0, 9999));
@@ -209,7 +316,7 @@
spake.reset(new Spakep224Exchanger(code));
break;
case CryptoType::kNone:
- if (is_security_disabled_) {
+ if (GetSettings().disable_security) {
spake.reset(new UnsecureKeyExchanger(code));
break;
}
@@ -317,6 +424,10 @@
return false;
}
+std::string SecurityManager::CreateSessionId() {
+ return Base64Encode(auth_manager_->CreateSessionId());
+}
+
void SecurityManager::RegisterPairingListeners(
const PairingStartListener& on_start,
const PairingEndListener& on_end) {
@@ -326,7 +437,7 @@
}
bool SecurityManager::CheckIfPairingAllowed(ErrorPtr* error) {
- if (is_security_disabled_)
+ if (GetSettings().disable_security)
return true;
if (block_pairing_until_ > auth_manager_->Now()) {
@@ -360,5 +471,17 @@
return confirmed_sessions_.erase(session_id) != 0;
}
+bool SecurityManager::IsAnonymousAuthSupported() const {
+ return GetSettings().local_anonymous_access_role != AuthScope::kNone;
+}
+
+bool SecurityManager::IsPairingAuthSupported() const {
+ return GetSettings().local_pairing_enabled;
+}
+
+bool SecurityManager::IsLocalAuthSupported() const {
+ return GetSettings().root_client_token_owner != RootClientTokenOwner::kNone;
+}
+
} // namespace privet
} // namespace weave
diff --git a/src/privet/security_manager.h b/src/privet/security_manager.h
index 93618d2..651089a 100644
--- a/src/privet/security_manager.h
+++ b/src/privet/security_manager.h
@@ -16,6 +16,7 @@
#include <base/memory/weak_ptr.h>
#include <weave/error.h>
+#include "src/config.h"
#include "src/privet/security_delegate.h"
namespace crypto {
@@ -51,25 +52,29 @@
virtual const std::string& GetKey() const = 0;
};
- SecurityManager(AuthManager* auth_manager,
- const std::set<PairingType>& pairing_modes,
- const std::string& embedded_code,
- bool disable_security,
+ SecurityManager(const Config* config,
+ AuthManager* auth_manager,
// TODO(vitalybuka): Remove task_runner.
provider::TaskRunner* task_runner);
~SecurityManager() override;
// SecurityDelegate methods
- std::string CreateAccessToken(const UserInfo& user_info) override;
- UserInfo ParseAccessToken(const std::string& token,
- base::Time* time) const override;
+ bool CreateAccessToken(AuthType auth_type,
+ const std::string& auth_code,
+ AuthScope desired_scope,
+ std::string* access_token,
+ AuthScope* access_token_scope,
+ base::TimeDelta* access_token_ttl,
+ ErrorPtr* error) override;
+ bool ParseAccessToken(const std::string& token,
+ UserInfo* user_info,
+ ErrorPtr* error) const override;
std::set<PairingType> GetPairingTypes() const override;
std::set<CryptoType> GetCryptoTypes() const override;
+ std::set<AuthType> GetAuthTypes() const override;
std::string ClaimRootClientAuthToken(ErrorPtr* error) override;
bool ConfirmClientAuthToken(const std::string& token,
ErrorPtr* error) override;
- bool IsValidPairingCode(const std::string& auth_code) const override;
-
bool StartPairing(PairingType mode,
CryptoType crypto,
std::string* session_id,
@@ -82,23 +87,38 @@
std::string* signature,
ErrorPtr* error) override;
bool CancelPairing(const std::string& session_id, ErrorPtr* error) override;
+ std::string CreateSessionId() override;
void RegisterPairingListeners(const PairingStartListener& on_start,
const PairingEndListener& on_end);
private:
+ const Config::Settings& GetSettings() const;
+ bool IsValidPairingCode(const std::vector<uint8_t>& auth_code) const;
FRIEND_TEST_ALL_PREFIXES(SecurityManagerTest, ThrottlePairing);
// Allows limited number of new sessions without successful authorization.
bool CheckIfPairingAllowed(ErrorPtr* error);
bool ClosePendingSession(const std::string& session_id);
bool CloseConfirmedSession(const std::string& session_id);
+ bool CreateAccessTokenImpl(AuthType auth_type,
+ const std::vector<uint8_t>& auth_code,
+ AuthScope desired_scope,
+ std::vector<uint8_t>* access_token,
+ AuthScope* access_token_scope,
+ base::TimeDelta* access_token_ttl,
+ ErrorPtr* error);
+ bool CreateAccessTokenImpl(AuthType auth_type,
+ AuthScope desired_scope,
+ std::vector<uint8_t>* access_token,
+ AuthScope* access_token_scope,
+ base::TimeDelta* access_token_ttl);
+ bool IsAnonymousAuthSupported() const;
+ bool IsPairingAuthSupported() const;
+ bool IsLocalAuthSupported() const;
+ const Config* config_{nullptr};
AuthManager* auth_manager_{nullptr};
- // If true allows unencrypted pairing and accepts any access code.
- bool is_security_disabled_{false};
- std::set<PairingType> pairing_modes_;
- std::string embedded_code_;
// TODO(vitalybuka): Session cleanup can be done without posting tasks.
provider::TaskRunner* task_runner_{nullptr};
std::map<std::string, std::unique_ptr<KeyExchanger>> pending_sessions_;
@@ -107,6 +127,7 @@
mutable base::Time block_pairing_until_;
PairingStartListener on_start_;
PairingEndListener on_end_;
+ uint64_t last_user_id_{0};
base::WeakPtrFactory<SecurityManager> weak_ptr_factory_{this};
diff --git a/src/privet/security_manager_unittest.cc b/src/privet/security_manager_unittest.cc
index 7c26026..43b7f00 100644
--- a/src/privet/security_manager_unittest.cc
+++ b/src/privet/security_manager_unittest.cc
@@ -20,7 +20,9 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <weave/provider/test/fake_task_runner.h>
+#include <weave/provider/test/mock_config_store.h>
+#include "src/config.h"
#include "src/data_encoding.h"
#include "src/privet/auth_manager.h"
#include "src/privet/openssl_utils.h"
@@ -57,6 +59,25 @@
} // namespace
+class SecurityManagerConfigStore : public provider::test::MockConfigStore {
+ public:
+ SecurityManagerConfigStore() {
+ EXPECT_CALL(*this, LoadDefaults(_))
+ .WillRepeatedly(testing::Invoke([](Settings* settings) {
+ settings->embedded_code = "1234";
+ settings->pairing_modes = {PairingType::kEmbeddedCode};
+ settings->client_id = "TEST_CLIENT_ID";
+ settings->client_secret = "TEST_CLIENT_SECRET";
+ settings->api_key = "TEST_API_KEY";
+ settings->oem_name = "TEST_OEM";
+ settings->model_name = "TEST_MODEL";
+ settings->model_id = "ABCDE";
+ settings->name = "TEST_NAME";
+ return true;
+ }));
+ }
+};
+
class SecurityManagerTest : public testing::Test {
protected:
void SetUp() override {
@@ -96,76 +117,60 @@
std::string auth_code_base64{Base64Encode(auth_code)};
- EXPECT_TRUE(security_.IsValidPairingCode(auth_code_base64));
+ std::string token;
+ AuthScope scope;
+ base::TimeDelta ttl;
+ EXPECT_TRUE(security_.CreateAccessToken(AuthType::kPairing,
+ auth_code_base64, AuthScope::kOwner,
+ &token, &scope, &ttl, nullptr));
+ EXPECT_EQ(AuthScope::kOwner, scope);
+ EXPECT_EQ(base::TimeDelta::FromHours(1), ttl);
+
+ UserInfo info;
+ EXPECT_TRUE(security_.ParseAccessToken(token, &info, nullptr));
+ EXPECT_EQ(AuthScope::kOwner, info.scope());
}
const base::Time time_ = base::Time::FromTimeT(1410000000);
provider::test::FakeTaskRunner task_runner_;
test::MockClock clock_;
+ SecurityManagerConfigStore config_store_;
+ Config config_{&config_store_};
AuthManager auth_manager_{
- {},
+ {22, 47, 23, 77, 42, 98, 96, 25, 83, 16, 9, 14, 91, 44, 15, 75, 60, 62,
+ 10, 18, 82, 35, 88, 100, 30, 45, 7, 46, 67, 84, 58, 85},
{
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,
},
+ {22, 47, 23, 77, 42, 98, 96, 25, 83, 16, 9, 14, 91, 44, 15, 75, 60, 62,
+ 10, 18, 82, 35, 88, 100, 30, 45, 7, 46, 67, 84, 58, 85},
&clock_};
- SecurityManager security_{&auth_manager_,
- {PairingType::kEmbeddedCode},
- "1234",
- false,
- &task_runner_};
+
+ SecurityManager security_{&config_, &auth_manager_, &task_runner_};
};
-TEST_F(SecurityManagerTest, IsBase64) {
- EXPECT_TRUE(
- IsBase64(security_.CreateAccessToken(UserInfo{AuthScope::kUser, 7})));
-}
+TEST_F(SecurityManagerTest, AccessToken) {
+ AuthScope scopes[] = {
+ AuthScope::kViewer, AuthScope::kUser, AuthScope::kManager,
+ AuthScope::kOwner,
+ };
+ for (size_t i = 1; i < 100; ++i) {
+ const AuthScope requested_scope = scopes[i % arraysize(scopes)];
+ std::string token;
+ AuthScope scope;
+ base::TimeDelta ttl;
+ EXPECT_TRUE(security_.CreateAccessToken(AuthType::kAnonymous, "",
+ requested_scope, &token, &scope,
+ &ttl, nullptr));
+ EXPECT_EQ(requested_scope, scope);
+ EXPECT_EQ(base::TimeDelta::FromHours(1), ttl);
-TEST_F(SecurityManagerTest, CreateSameToken) {
- EXPECT_EQ(security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}),
- security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}));
-}
-
-TEST_F(SecurityManagerTest, CreateTokenDifferentScope) {
- EXPECT_NE(security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 456}),
- security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}));
-}
-
-TEST_F(SecurityManagerTest, CreateTokenDifferentUser) {
- EXPECT_NE(security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}),
- security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 789}));
-}
-
-TEST_F(SecurityManagerTest, CreateTokenDifferentTime) {
- auto token = security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567});
- EXPECT_CALL(clock_, Now())
- .WillRepeatedly(Return(base::Time::FromTimeT(1400000000)));
- EXPECT_NE(token,
- security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567}));
-}
-
-TEST_F(SecurityManagerTest, CreateTokenDifferentInstance) {
- AuthManager auth{{}, {}, &clock_};
- EXPECT_NE(security_.CreateAccessToken(UserInfo{AuthScope::kUser, 123}),
- SecurityManager(&auth, {}, "", false, &task_runner_)
- .CreateAccessToken(UserInfo{AuthScope::kUser, 123}));
-}
-
-TEST_F(SecurityManagerTest, ParseAccessToken) {
- // Multiple attempts with random secrets.
- for (size_t i = 0; i < 1000; ++i) {
- AuthManager auth{{}, {}, &clock_};
- SecurityManager security{&auth, {}, "", false, &task_runner_};
-
- std::string token =
- security.CreateAccessToken(UserInfo{AuthScope::kUser, 5});
- base::Time time2;
- EXPECT_EQ(AuthScope::kUser,
- security.ParseAccessToken(token, &time2).scope());
- EXPECT_EQ(5u, security.ParseAccessToken(token, &time2).user_id());
- // Token timestamp resolution is one second.
- EXPECT_GE(1, std::abs((clock_.Now() - time2).InSeconds()));
+ UserInfo info;
+ EXPECT_TRUE(security_.ParseAccessToken(token, &info, nullptr));
+ EXPECT_EQ(requested_scope, info.scope());
+ EXPECT_EQ("0/" + std::to_string(i), info.user_id());
}
}